@gobing-ai/knowledge-kit 0.0.28 → 0.0.30

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.
Files changed (97) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +114 -38
  3. package/package.json +1 -1
  4. package/plugins/generations/daily-article-gen/dist/index.js +45 -5
  5. package/plugins/generations/daily-article-gen/src/index.ts +59 -4
  6. package/plugins/generations/dailynews-gen/dist/index.js +46 -9
  7. package/plugins/generations/dailynews-gen/src/index.ts +36 -1
  8. package/plugins/generations/dailynews-gen/src/script-builder.ts +15 -12
  9. package/plugins/generations/dailynews-gen/src/script-plan.ts +21 -3
  10. package/plugins/generations/episode-plan-gen/dist/index.js +55 -4
  11. package/plugins/generations/episode-plan-gen/src/index.ts +80 -2
  12. package/plugins/generations/image-gen/assets/fonts/NotoSansSC-Bold.otf +0 -0
  13. package/plugins/generations/image-gen/assets/fonts/NotoSansSC-Regular.otf +0 -0
  14. package/plugins/generations/image-gen/assets/fonts/OFL.txt +92 -0
  15. package/plugins/generations/image-gen/assets/fonts/SOURCE.md +15 -0
  16. package/plugins/generations/image-gen/dist/index.js +700 -22
  17. package/plugins/generations/image-gen/package.json +1 -0
  18. package/plugins/generations/image-gen/presets/formats/daily-news-cover.yaml +2 -2
  19. package/plugins/generations/image-gen/presets/formats/xhs-card.yaml +19 -11
  20. package/plugins/generations/image-gen/presets/styles/xhs-knowledge.yaml +2 -1
  21. package/plugins/generations/image-gen/scripts/compose-e2e-artifact.ts +135 -0
  22. package/plugins/generations/image-gen/src/bytes.ts +116 -0
  23. package/plugins/generations/image-gen/src/compose/card.ts +294 -0
  24. package/plugins/generations/image-gen/src/compose/chromium.ts +147 -0
  25. package/plugins/generations/image-gen/src/compose/cmap.ts +177 -0
  26. package/plugins/generations/image-gen/src/index.ts +211 -13
  27. package/plugins/generations/image-gen/src/job.ts +12 -0
  28. package/plugins/generations/image-gen/src/presets.ts +53 -13
  29. package/plugins/generations/image-gen/src/providers/minimax.ts +12 -4
  30. package/plugins/generations/image-gen/src/providers/types.ts +12 -1
  31. package/plugins/kk/plugin.json +1 -1
  32. package/plugins/kk/scripts/daily-voice-style.zh.md +27 -14
  33. package/plugins/kk/scripts/itc-stages.ts +21 -3
  34. package/plugins/kk/scripts/kk-workflow-stages.ts +461 -10
  35. package/plugins/kk/scripts/topic-boundary.json +96 -0
  36. package/plugins/kk/scripts/validate-voicescript.ts +15 -0
  37. package/plugins/kk/skills/content-judge/SKILL.md +30 -0
  38. package/plugins/kk/skills/content-judge/references/workflow-integration.md +9 -2
  39. package/plugins/kk/skills/content-safety/SKILL.md +163 -0
  40. package/plugins/kk/skills/content-safety/references/platforms/apple-podcasts.md +36 -0
  41. package/plugins/kk/skills/content-safety/references/platforms/netease-music.md +31 -0
  42. package/plugins/kk/skills/content-safety/references/platforms/wechatmp.md +32 -0
  43. package/plugins/kk/skills/content-safety/references/platforms/xiaoyuzhou.md +35 -0
  44. package/plugins/kk/skills/publish/SKILL.md +1 -1
  45. package/plugins/kk/skills/xhs-images/SKILL.md +11 -7
  46. package/plugins/kk/skills/xhs-images/references/card-layouts.md +4 -4
  47. package/plugins/kk/workflows/judge-gated-publish-example.yaml +18 -10
  48. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +139 -24
  49. package/plugins/kk/workflows/kk-itc.yaml +21 -8
  50. package/plugins/publishings/podcast-pub/dist/index.js +4 -0
  51. package/plugins/publishings/podcast-pub/src/index.ts +2 -0
  52. package/plugins/publishings/podcast-pub/src/show-notes.ts +6 -0
  53. package/plugins/publishings/xhs-pub/dist/index.js +1 -1
  54. package/plugins/publishings/xhs-pub/src/automation-guard.ts +2 -2
  55. package/plugins/publishings/xhs-pub/src/index.ts +1 -1
  56. package/plugins/publishings/xhs-pub/src/profile-check.ts +2 -2
  57. package/plugins/sp/lib/env.ts +0 -46
  58. package/plugins/sp/lib/inline-run.generated.mjs +0 -1489
  59. package/plugins/sp/scripts/batch-preflight.mjs +0 -346
  60. package/plugins/sp/scripts/batch-preflight.ts +0 -459
  61. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +0 -615
  62. package/plugins/sp/scripts/daily-summary/daily-summary.ts +0 -846
  63. package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
  64. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +0 -223
  65. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +0 -367
  66. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +0 -132
  67. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +0 -169
  68. package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -171
  69. package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
  70. package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -285
  71. package/plugins/sp/scripts/feature-sync-bounded.ts +0 -478
  72. package/plugins/sp/scripts/feature-verification-steps.ts +0 -275
  73. package/plugins/sp/scripts/history-anatomy-cache.mjs +0 -902
  74. package/plugins/sp/scripts/history-anatomy-cache.ts +0 -1028
  75. package/plugins/sp/scripts/idea-handoff.mjs +0 -22
  76. package/plugins/sp/scripts/idea-handoff.ts +0 -44
  77. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -185
  78. package/plugins/sp/scripts/inline-run-setup.ts +0 -722
  79. package/plugins/sp/scripts/pr-reviewing.mjs +0 -769
  80. package/plugins/sp/scripts/pr-reviewing.ts +0 -925
  81. package/plugins/sp/scripts/quality-gate.mjs +0 -179
  82. package/plugins/sp/scripts/quality-gate.ts +0 -217
  83. package/plugins/sp/scripts/script-contract-check.ts +0 -319
  84. package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
  85. package/plugins/sp/scripts/surface-drift-inventory.ts +0 -929
  86. package/plugins/sp/scripts/task-evidence-precheck.ts +0 -181
  87. package/plugins/sp/scripts/task-size-precheck.ts +0 -175
  88. package/plugins/sp/scripts/transition-shim-check.ts +0 -238
  89. package/plugins/sp/scripts/validate-commands.ts +0 -689
  90. package/plugins/sp/scripts/validate-flag-contracts.ts +0 -878
  91. package/plugins/sp/scripts/verify-answer-lint.ts +0 -530
  92. package/plugins/sp/scripts/workflow-step-profile.mjs +0 -316
  93. package/plugins/sp/scripts/workflow-step-profile.ts +0 -456
  94. package/plugins/sp/scripts/wrapup-drift-probe.mjs +0 -190
  95. package/plugins/sp/scripts/wrapup-drift-probe.ts +0 -258
  96. package/plugins/sp/scripts/wrapup-steps.mjs +0 -444
  97. package/plugins/sp/scripts/wrapup-steps.ts +0 -552
@@ -21809,8 +21809,410 @@ function findProjectRoot(startDir) {
21809
21809
  }
21810
21810
  var init_file_system_node = () => {};
21811
21811
 
21812
+ // ../../plugins/generations/image-gen/src/bytes.ts
21813
+ import { readFile, writeFile } from "fs/promises";
21814
+ async function writeBytes(path2, bytes) {
21815
+ await writeFile(path2, bytes);
21816
+ }
21817
+ async function readBytes(path2) {
21818
+ return new Uint8Array(await readFile(path2));
21819
+ }
21820
+ function imageDimensions(bytes) {
21821
+ if (isPng(bytes))
21822
+ return pngDimensions(bytes);
21823
+ if (isJpeg(bytes))
21824
+ return jpegDimensions(bytes);
21825
+ if (isWebp(bytes))
21826
+ return webpDimensions(bytes);
21827
+ return null;
21828
+ }
21829
+ function isPng(b) {
21830
+ const PNG_SIG = [137, 80, 78, 71, 13, 10, 26, 10];
21831
+ if (b.byteLength < 24)
21832
+ return false;
21833
+ for (let i = 0;i < 8; i++) {
21834
+ if (b[i] !== PNG_SIG[i])
21835
+ return false;
21836
+ }
21837
+ return true;
21838
+ }
21839
+ function pngDimensions(b) {
21840
+ const view = new DataView(b.buffer, b.byteOffset, b.byteLength);
21841
+ const ihdrLength = view.getUint32(8, false);
21842
+ if (ihdrLength !== 13 || view.getUint32(12, false) !== 1229472850) {
21843
+ return null;
21844
+ }
21845
+ return { width: view.getUint32(16, false), height: view.getUint32(20, false) };
21846
+ }
21847
+ function isJpeg(b) {
21848
+ const view = new DataView(b.buffer, b.byteOffset, b.byteLength);
21849
+ return b.byteLength >= 4 && view.getUint16(0, false) === 65496 && view.getUint8(2) === 255;
21850
+ }
21851
+ function jpegDimensions(b) {
21852
+ const view = new DataView(b.buffer, b.byteOffset, b.byteLength);
21853
+ let at = 2;
21854
+ while (at + 4 <= b.byteLength) {
21855
+ if (view.getUint8(at) !== 255)
21856
+ return null;
21857
+ const marker = view.getUint8(at + 1);
21858
+ at += 2;
21859
+ if (marker === 1 || marker >= 208 && marker <= 215)
21860
+ continue;
21861
+ if (at + 2 > b.byteLength)
21862
+ return null;
21863
+ const length = view.getUint16(at, false);
21864
+ if (length < 2)
21865
+ return null;
21866
+ const isSof = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
21867
+ if (isSof) {
21868
+ if (at + 7 > b.byteLength)
21869
+ return null;
21870
+ return { height: view.getUint16(at + 3, false), width: view.getUint16(at + 5, false) };
21871
+ }
21872
+ at += length;
21873
+ }
21874
+ return null;
21875
+ }
21876
+ function isWebp(b) {
21877
+ if (b.byteLength < 20)
21878
+ return false;
21879
+ const view = new DataView(b.buffer, b.byteOffset, b.byteLength);
21880
+ return view.getUint32(0, true) === 1179011410 && view.getUint32(8, true) === 1346520407;
21881
+ }
21882
+ function webpDimensions(b) {
21883
+ const view = new DataView(b.buffer, b.byteOffset, b.byteLength);
21884
+ let at = 12;
21885
+ while (at + 8 <= b.byteLength) {
21886
+ const id = String.fromCharCode(view.getUint8(at), view.getUint8(at + 1), view.getUint8(at + 2), view.getUint8(at + 3));
21887
+ const size = view.getUint32(at + 4, true);
21888
+ const body = at + 8;
21889
+ if (id === "VP8 ") {
21890
+ if (size < 10 || body + 10 > b.byteLength)
21891
+ return null;
21892
+ if (view.getUint8(body + 3) !== 157 || view.getUint8(body + 4) !== 1 || view.getUint8(body + 5) !== 42) {
21893
+ return null;
21894
+ }
21895
+ return {
21896
+ width: view.getUint16(body + 6, true) & 16383,
21897
+ height: view.getUint16(body + 8, true) & 16383
21898
+ };
21899
+ }
21900
+ if (id === "VP8L") {
21901
+ if (size < 5 || body + 5 > b.byteLength || view.getUint8(body) !== 47)
21902
+ return null;
21903
+ const bits = view.getUint32(body + 1, true);
21904
+ return { width: (bits & 16383) + 1, height: (bits >>> 14 & 16383) + 1 };
21905
+ }
21906
+ if (id === "VP8X") {
21907
+ if (size < 10 || body + 10 > b.byteLength)
21908
+ return null;
21909
+ const width = view.getUint8(body + 4) | view.getUint8(body + 5) << 8 | view.getUint8(body + 6) << 16;
21910
+ const height = view.getUint8(body + 7) | view.getUint8(body + 8) << 8 | view.getUint8(body + 9) << 16;
21911
+ return { width: width + 1, height: height + 1 };
21912
+ }
21913
+ at = body + size + size % 2;
21914
+ }
21915
+ return null;
21916
+ }
21917
+ var init_bytes = () => {};
21918
+
21919
+ // ../../plugins/generations/image-gen/src/compose/card.ts
21920
+ function assertCoverage(input, fonts) {
21921
+ const headlineCover = fonts.get(input.compose.fonts.headline);
21922
+ const bodyCover = fonts.get(input.compose.fonts.body);
21923
+ if (headlineCover === undefined) {
21924
+ throw new Error(`compose: headline font '${input.compose.fonts.headline}' not loaded`);
21925
+ }
21926
+ if (bodyCover === undefined) {
21927
+ throw new Error(`compose: body font '${input.compose.fonts.body}' not loaded`);
21928
+ }
21929
+ const checkField = (text, fieldName, covers) => {
21930
+ for (const ch of text) {
21931
+ const cp = ch.codePointAt(0);
21932
+ if (cp === undefined)
21933
+ continue;
21934
+ if (cp <= 32 || cp === 160 || cp >= 8192 && cp <= 8207 || cp === 12288) {
21935
+ continue;
21936
+ }
21937
+ if (!covers(cp)) {
21938
+ const hex3 = `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`;
21939
+ const fontName = fieldName.startsWith("points") ? input.compose.fonts.body : input.compose.fonts.headline;
21940
+ throw new Error(`compose: ${fieldName} has ${hex3} '${ch}' not covered by ${fontName}`);
21941
+ }
21942
+ }
21943
+ };
21944
+ checkField(input.headline, "headline", headlineCover);
21945
+ for (const [i, point] of input.points.entries()) {
21946
+ checkField(point, `points[${i}]`, bodyCover);
21947
+ }
21948
+ }
21949
+ function assertTextFreePrompt(prompt, copy, sources) {
21950
+ const hit = (text) => {
21951
+ const headline = copy.headline.trim();
21952
+ if (headline.length >= 2 && text.includes(headline))
21953
+ return `the headline '${headline}'`;
21954
+ for (const point of copy.points) {
21955
+ const trimmed = point.trim();
21956
+ if (trimmed.length >= 2 && text.includes(trimmed))
21957
+ return `point '${trimmed}'`;
21958
+ }
21959
+ const han = /\p{Script=Han}/u.exec(text);
21960
+ if (han !== null) {
21961
+ const cp = (han[0]?.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0");
21962
+ return `Han character '${han[0]}' (U+${cp})`;
21963
+ }
21964
+ return null;
21965
+ };
21966
+ const fail = (source, what2) => {
21967
+ throw new Error(`compose: the illustration prompt must stay text-free \u2014 ${source} contributes ${what2} (card copy is typeset, never prompted)`);
21968
+ };
21969
+ for (const { source, text } of sources) {
21970
+ const what2 = hit(text);
21971
+ if (what2 !== null)
21972
+ fail(source, what2);
21973
+ }
21974
+ const what = hit(prompt);
21975
+ if (what !== null)
21976
+ fail("the composed prompt", what);
21977
+ }
21978
+ function cardHtml(input, fontUrls) {
21979
+ const { compose: cfg, width, height, illustration } = input;
21980
+ const { safeArea, headlineMinPx, bodyMinPx, illustrationHeight, palette } = cfg;
21981
+ const headlineFont = fontUrls.get(cfg.fonts.headline) ?? "";
21982
+ const bodyFont = fontUrls.get(cfg.fonts.body) ?? "";
21983
+ const headlineStartPx = headlineMinPx * 1.5;
21984
+ const textPanelHeight = height - illustrationHeight;
21985
+ const safeWidth = width - safeArea * 2;
21986
+ const illustrationSrc = illustration ? `data:${illustration.mime};base64,${Buffer.from(illustration.bytes).toString("base64")}` : "";
21987
+ const pointsHtml = input.points.map((pt, i) => `<div class="point" data-field="points[${i}]" style="font-size:${bodyMinPx}px;">${escHtml(pt)}</div>`).join(`
21988
+ `);
21989
+ return `<!DOCTYPE html>
21990
+ <html lang="zh-Hans">
21991
+ <head>
21992
+ <meta charset="utf-8">
21993
+ <style>
21994
+ @font-face {
21995
+ font-family: 'NotoSansSC-Bold';
21996
+ src: url('${headlineFont}') format('opentype');
21997
+ font-weight: bold;
21998
+ font-style: normal;
21999
+ }
22000
+ @font-face {
22001
+ font-family: 'NotoSansSC-Regular';
22002
+ src: url('${bodyFont}') format('opentype');
22003
+ font-weight: normal;
22004
+ font-style: normal;
22005
+ }
22006
+ * { box-sizing: border-box; margin: 0; padding: 0; }
22007
+ html, body {
22008
+ width: ${width}px;
22009
+ height: ${height}px;
22010
+ overflow: hidden;
22011
+ background: ${palette.background};
22012
+ }
22013
+ .card {
22014
+ width: ${width}px;
22015
+ height: ${height}px;
22016
+ display: flex;
22017
+ flex-direction: column;
22018
+ background: ${palette.background};
22019
+ }
22020
+ .illustration-band {
22021
+ width: ${width}px;
22022
+ height: ${illustrationHeight}px;
22023
+ flex-shrink: 0;
22024
+ overflow: hidden;
22025
+ background: ${palette.background};
22026
+ }
22027
+ .illustration-band img {
22028
+ width: 100%;
22029
+ height: 100%;
22030
+ object-fit: cover;
22031
+ display: block;
22032
+ }
22033
+ .text-panel {
22034
+ width: ${width}px;
22035
+ height: ${textPanelHeight}px;
22036
+ flex-shrink: 0;
22037
+ padding: ${safeArea}px;
22038
+ display: flex;
22039
+ flex-direction: column;
22040
+ justify-content: flex-start;
22041
+ overflow: hidden;
22042
+ background: ${palette.background};
22043
+ }
22044
+ .headline {
22045
+ font-family: 'NotoSansSC-Bold', sans-serif;
22046
+ font-weight: bold;
22047
+ color: ${palette.text};
22048
+ line-height: 1.3;
22049
+ margin-bottom: ${Math.round(safeArea / 2)}px;
22050
+ word-break: break-all;
22051
+ white-space: normal;
22052
+ /* font-size is set inline with JS fit-step */
22053
+ font-size: ${headlineStartPx}px;
22054
+ }
22055
+ .points-container {
22056
+ display: flex;
22057
+ flex-direction: column;
22058
+ gap: ${Math.round(bodyMinPx * 0.5)}px;
22059
+ }
22060
+ .point {
22061
+ font-family: 'NotoSansSC-Regular', sans-serif;
22062
+ font-weight: normal;
22063
+ color: ${palette.text};
22064
+ line-height: 1.5;
22065
+ word-break: break-all;
22066
+ white-space: normal;
22067
+ }
22068
+ .safe-area-sentinel {
22069
+ position: absolute;
22070
+ top: ${illustrationHeight + safeArea}px;
22071
+ left: ${safeArea}px;
22072
+ width: ${safeWidth}px;
22073
+ height: ${textPanelHeight - safeArea * 2}px;
22074
+ pointer-events: none;
22075
+ }
22076
+ </style>
22077
+ </head>
22078
+ <body>
22079
+ <div class="card">
22080
+ <div class="illustration-band">
22081
+ ${illustrationSrc ? `<img src="${illustrationSrc}" alt="">` : `<div style="width:100%;height:100%;background:${palette.background};"></div>`}
22082
+ </div>
22083
+ <div class="text-panel">
22084
+ <div class="headline" data-field="headline" id="headline">${escHtml(input.headline)}</div>
22085
+ <div class="points-container">
22086
+ ${pointsHtml}
22087
+ </div>
22088
+ </div>
22089
+ </div>
22090
+ <div class="safe-area-sentinel" id="safe-area-sentinel"></div>
22091
+ <script>
22092
+ (function () {
22093
+ // Step headline font down from start until it fits, never below floor.
22094
+ var headline = document.getElementById('headline');
22095
+ var headlineMinPx = ${headlineMinPx};
22096
+ var headlineStartPx = ${headlineStartPx};
22097
+ var safeWidth = ${safeWidth};
22098
+ var panelHeight = ${textPanelHeight - safeArea * 2};
22099
+ var size = headlineStartPx;
22100
+ while (size > headlineMinPx) {
22101
+ headline.style.fontSize = size + 'px';
22102
+ if (headline.scrollWidth <= safeWidth && headline.scrollHeight <= panelHeight) break;
22103
+ size -= 4;
22104
+ }
22105
+ headline.style.fontSize = Math.max(size, headlineMinPx) + 'px';
22106
+ })();
22107
+ </script>
22108
+ </body>
22109
+ </html>`;
22110
+ }
22111
+ function escHtml(s) {
22112
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
22113
+ }
22114
+
22115
+ // ../../plugins/generations/image-gen/src/compose/chromium.ts
22116
+ var exports_chromium = {};
22117
+ __export(exports_chromium, {
22118
+ makeChromiumComposer: () => makeChromiumComposer,
22119
+ ChromiumComposer: () => ChromiumComposer
22120
+ });
22121
+ import { join as join3 } from "path";
22122
+ function fontRouteUrls(fontNames) {
22123
+ const map2 = new Map;
22124
+ for (const name of fontNames) {
22125
+ map2.set(name, `${FONT_ROUTE_BASE}${encodeURIComponent(name)}`);
22126
+ }
22127
+ return map2;
22128
+ }
22129
+
22130
+ class ChromiumComposer {
22131
+ async compose(input) {
22132
+ let chromium;
22133
+ try {
22134
+ const pw = await import("playwright");
22135
+ chromium = pw.chromium;
22136
+ } catch (err) {
22137
+ throw new Error(`compose: Playwright is not installed. Run: bunx playwright install chromium
22138
+ (${err instanceof Error ? err.message : String(err)})`);
22139
+ }
22140
+ const fontNames = [input.compose.fonts.headline, input.compose.fonts.body];
22141
+ const fontUrls = fontRouteUrls(fontNames);
22142
+ const html = cardHtml(input, fontUrls);
22143
+ let browser;
22144
+ try {
22145
+ try {
22146
+ browser = await chromium.launch({ headless: true });
22147
+ } catch (err) {
22148
+ const msg = err instanceof Error ? err.message : String(err);
22149
+ throw new Error(`compose: Chromium browser not found. Run: bunx playwright install chromium
22150
+ (${msg})`);
22151
+ }
22152
+ const page = await browser.newPage({
22153
+ viewport: { width: input.width, height: input.height },
22154
+ deviceScaleFactor: 1
22155
+ });
22156
+ await page.route(`${FONT_ROUTE_BASE}**`, async (route) => {
22157
+ const url2 = new URL(route.request().url());
22158
+ const fontName = decodeURIComponent(url2.pathname.slice(1));
22159
+ const fontPath = join3(FONTS_DIR, fontName);
22160
+ try {
22161
+ const bytes = await readBytes(fontPath);
22162
+ await route.fulfill({
22163
+ status: 200,
22164
+ contentType: "font/otf",
22165
+ body: Buffer.from(bytes)
22166
+ });
22167
+ } catch {
22168
+ await route.abort();
22169
+ }
22170
+ });
22171
+ await page.setContent(html, { waitUntil: "domcontentloaded" });
22172
+ await page.evaluate("document.fonts.ready");
22173
+ const { safeArea, illustrationHeight } = input.compose;
22174
+ const safeLeft = safeArea;
22175
+ const safeTop = illustrationHeight + safeArea;
22176
+ const safeRight = input.width - safeArea;
22177
+ const safeBottom = input.height - safeArea;
22178
+ const overflowExpr = `
22179
+ (function(safeLeft, safeTop, safeRight, safeBottom) {
22180
+ var results = [];
22181
+ var els = Array.from(document.querySelectorAll('[data-field]'));
22182
+ for (var i = 0; i < els.length; i++) {
22183
+ var el = els[i];
22184
+ var field = el.dataset.field || '';
22185
+ var rect = el.getBoundingClientRect();
22186
+ if (rect.left < safeLeft || rect.top < safeTop || rect.right > safeRight || rect.bottom > safeBottom) {
22187
+ results.push({ field: field, rect: { top: rect.top, left: rect.left, bottom: rect.bottom, right: rect.right } });
22188
+ }
22189
+ }
22190
+ return results;
22191
+ })(${safeLeft}, ${safeTop}, ${safeRight}, ${safeBottom})
22192
+ `;
22193
+ const overflows = await page.evaluate(overflowExpr);
22194
+ if (overflows.length > 0) {
22195
+ const fields = overflows.map((o) => o.field).join(", ");
22196
+ throw new Error(`compose: text overflows the safe area for field(s): ${fields} (safe area: ${safeArea}px inset)`);
22197
+ }
22198
+ const screenshot = await page.screenshot({ type: "png", animations: "disabled" });
22199
+ return new Uint8Array(screenshot);
22200
+ } finally {
22201
+ await browser?.close();
22202
+ }
22203
+ }
22204
+ }
22205
+ function makeChromiumComposer() {
22206
+ return new ChromiumComposer;
22207
+ }
22208
+ var FONTS_DIR, FONT_ROUTE_BASE = "http://kk-fonts.local/";
22209
+ var init_chromium = __esm(() => {
22210
+ init_bytes();
22211
+ FONTS_DIR = join3(import.meta.dir, "..", "..", "assets", "fonts");
22212
+ });
22213
+
21812
22214
  // ../../plugins/generations/image-gen/src/index.ts
21813
- import { dirname as dirname3 } from "path";
22215
+ import { dirname as dirname3, join as join4 } from "path";
21814
22216
  import { parseArgs } from "util";
21815
22217
 
21816
22218
  // ../../packages/kk-core/src/kinds.ts
@@ -21908,14 +22310,141 @@ init_config();
21908
22310
  if (false) {}
21909
22311
  // ../../plugins/generations/image-gen/src/index.ts
21910
22312
  init_dist();
22313
+ init_bytes();
21911
22314
 
21912
- // ../../plugins/generations/image-gen/src/bytes.ts
21913
- import { readFile, writeFile } from "fs/promises";
21914
- async function writeBytes(path2, bytes) {
21915
- await writeFile(path2, bytes);
22315
+ // ../../plugins/generations/image-gen/src/compose/cmap.ts
22316
+ function u16(view, offset) {
22317
+ return view.getUint16(offset, false);
22318
+ }
22319
+ function u32(view, offset) {
22320
+ return view.getUint32(offset, false);
22321
+ }
22322
+ function tag(view, offset) {
22323
+ return String.fromCharCode(view.getUint8(offset), view.getUint8(offset + 1), view.getUint8(offset + 2), view.getUint8(offset + 3));
22324
+ }
22325
+ function assertBytes(view, offset, needed, context2) {
22326
+ if (offset + needed > view.byteLength) {
22327
+ throw new Error(`compose: truncated font \u2014 expected ${needed} bytes at offset ${offset} (${context2})`);
22328
+ }
22329
+ }
22330
+ function findCmapTable(view) {
22331
+ assertBytes(view, 0, 12, "sfnt header");
22332
+ const magic = u32(view, 0);
22333
+ if (magic !== 65536 && magic !== 1330926671 && magic !== 1953658213) {
22334
+ throw new Error(`compose: not a valid sfnt font (magic 0x${magic.toString(16).padStart(8, "0")})`);
22335
+ }
22336
+ const numTables = u16(view, 4);
22337
+ assertBytes(view, 12, numTables * 16, "sfnt table directory");
22338
+ for (let i = 0;i < numTables; i++) {
22339
+ const base = 12 + i * 16;
22340
+ if (tag(view, base) === "cmap") {
22341
+ const offset = u32(view, base + 8);
22342
+ const length = u32(view, base + 12);
22343
+ return { offset, length };
22344
+ }
22345
+ }
22346
+ throw new Error("compose: truncated font \u2014 cmap table not found in sfnt directory");
22347
+ }
22348
+ function coverageFormat4(view, subtableOffset) {
22349
+ assertBytes(view, subtableOffset, 14, "cmap format 4 header");
22350
+ const length = u16(view, subtableOffset + 2);
22351
+ assertBytes(view, subtableOffset, length, "cmap format 4 body");
22352
+ const segCount = u16(view, subtableOffset + 6) / 2;
22353
+ const endCodesBase = subtableOffset + 14;
22354
+ assertBytes(view, endCodesBase, segCount * 2 + 2 + segCount * 2 + segCount * 2 + segCount * 2, "cmap format 4 segments");
22355
+ const startCodesBase = endCodesBase + segCount * 2 + 2;
22356
+ const idDeltaBase = startCodesBase + segCount * 2;
22357
+ const idRangeOffsetBase = idDeltaBase + segCount * 2;
22358
+ return (cp) => {
22359
+ if (cp > 65535)
22360
+ return false;
22361
+ let lo = 0;
22362
+ let hi = segCount - 1;
22363
+ while (lo <= hi) {
22364
+ const mid = lo + hi >>> 1;
22365
+ const endCode = u16(view, endCodesBase + mid * 2);
22366
+ if (endCode < cp) {
22367
+ lo = mid + 1;
22368
+ } else {
22369
+ hi = mid - 1;
22370
+ }
22371
+ }
22372
+ if (lo >= segCount)
22373
+ return false;
22374
+ const startCode = u16(view, startCodesBase + lo * 2);
22375
+ if (cp < startCode)
22376
+ return false;
22377
+ const idRangeOffset = u16(view, idRangeOffsetBase + lo * 2);
22378
+ if (idRangeOffset === 0) {
22379
+ const idDelta = u16(view, idDeltaBase + lo * 2);
22380
+ return (cp + idDelta & 65535) !== 0;
22381
+ }
22382
+ const glyphIdAddr = idRangeOffsetBase + lo * 2 + idRangeOffset + (cp - startCode) * 2;
22383
+ if (glyphIdAddr + 2 > view.byteLength)
22384
+ return false;
22385
+ const glyphId = u16(view, glyphIdAddr);
22386
+ return glyphId !== 0;
22387
+ };
22388
+ }
22389
+ function coverageFormat12(view, subtableOffset) {
22390
+ assertBytes(view, subtableOffset, 16, "cmap format 12 header");
22391
+ const numGroups = u32(view, subtableOffset + 12);
22392
+ assertBytes(view, subtableOffset + 16, numGroups * 12, "cmap format 12 groups");
22393
+ const groupsBase = subtableOffset + 16;
22394
+ return (cp) => {
22395
+ let lo = 0;
22396
+ let hi = numGroups - 1;
22397
+ while (lo <= hi) {
22398
+ const mid = lo + hi >>> 1;
22399
+ const base = groupsBase + mid * 12;
22400
+ const startChar = u32(view, base);
22401
+ const endChar = u32(view, base + 4);
22402
+ if (endChar < cp) {
22403
+ lo = mid + 1;
22404
+ } else if (startChar > cp) {
22405
+ hi = mid - 1;
22406
+ } else {
22407
+ const startGlyph = u32(view, base + 8);
22408
+ return startGlyph + (cp - startChar) !== 0;
22409
+ }
22410
+ }
22411
+ return false;
22412
+ };
21916
22413
  }
21917
- async function readBytes(path2) {
21918
- return new Uint8Array(await readFile(path2));
22414
+ function glyphCoverage(fontBytes) {
22415
+ if (fontBytes.byteLength < 12) {
22416
+ throw new Error("compose: truncated font \u2014 too small for sfnt header");
22417
+ }
22418
+ const view = new DataView(fontBytes.buffer, fontBytes.byteOffset, fontBytes.byteLength);
22419
+ const { offset: cmapOffset } = findCmapTable(view);
22420
+ assertBytes(view, cmapOffset, 4, "cmap table header");
22421
+ const numSubtables = u16(view, cmapOffset + 2);
22422
+ assertBytes(view, cmapOffset + 4, numSubtables * 8, "cmap encoding records");
22423
+ let fmt4Offset;
22424
+ let fmt12Offset;
22425
+ for (let i = 0;i < numSubtables; i++) {
22426
+ const recBase = cmapOffset + 4 + i * 8;
22427
+ const platformId = u16(view, recBase);
22428
+ const encodingId = u16(view, recBase + 2);
22429
+ const subtableOffset = cmapOffset + u32(view, recBase + 4);
22430
+ if (subtableOffset + 2 > view.byteLength)
22431
+ continue;
22432
+ const fmt = u16(view, subtableOffset);
22433
+ if (fmt === 12 && (platformId === 3 || platformId === 0)) {
22434
+ fmt12Offset = subtableOffset;
22435
+ }
22436
+ if (fmt === 4 && (platformId === 3 || platformId === 0) && encodingId !== 10) {
22437
+ if (fmt4Offset === undefined)
22438
+ fmt4Offset = subtableOffset;
22439
+ }
22440
+ }
22441
+ if (fmt12Offset !== undefined) {
22442
+ return coverageFormat12(view, fmt12Offset);
22443
+ }
22444
+ if (fmt4Offset !== undefined) {
22445
+ return coverageFormat4(view, fmt4Offset);
22446
+ }
22447
+ throw new Error("compose: truncated font \u2014 no usable cmap subtable (format 4 or 12) found");
21919
22448
  }
21920
22449
 
21921
22450
  // ../../plugins/generations/image-gen/src/job.ts
@@ -21993,6 +22522,15 @@ var PresetSourceSchema = exports_external.object({
21993
22522
  extractedAt: exports_external.string().optional(),
21994
22523
  confidence: exports_external.enum(["high", "medium", "low"]).optional()
21995
22524
  });
22525
+ var ComposeConfigSchema = exports_external.object({
22526
+ engine: exports_external.literal("text-card"),
22527
+ safeArea: exports_external.number(),
22528
+ headlineMinPx: exports_external.number(),
22529
+ bodyMinPx: exports_external.number(),
22530
+ illustrationHeight: exports_external.number(),
22531
+ palette: exports_external.object({ background: exports_external.string(), text: exports_external.string(), accent: exports_external.string() }),
22532
+ fonts: exports_external.object({ headline: exports_external.string(), body: exports_external.string() })
22533
+ });
21996
22534
  var FormatPresetSchema = exports_external.object({
21997
22535
  name: exports_external.string(),
21998
22536
  description: exports_external.string(),
@@ -22004,7 +22542,10 @@ var FormatPresetSchema = exports_external.object({
22004
22542
  keywords: exports_external.array(exports_external.string()),
22005
22543
  outputFilename: exports_external.string(),
22006
22544
  variables: exports_external.record(exports_external.string(), exports_external.object({ description: exports_external.string(), default: exports_external.string() })),
22007
- template: exports_external.string()
22545
+ template: exports_external.string(),
22546
+ compose: ComposeConfigSchema.optional()
22547
+ }).refine((data) => data.compose === undefined || data.width !== undefined && data.height !== undefined, {
22548
+ message: "A format preset with a compose block requires both width and height to be declared"
22008
22549
  });
22009
22550
  var StylePresetSchema = exports_external.object({
22010
22551
  name: exports_external.string(),
@@ -22157,6 +22698,7 @@ function loadPresets() {
22157
22698
  import { Buffer as Buffer3 } from "buffer";
22158
22699
 
22159
22700
  // ../../plugins/generations/image-gen/src/providers/refs.ts
22701
+ init_bytes();
22160
22702
  import { extname as extname2 } from "path";
22161
22703
  function mimeForPath(path2) {
22162
22704
  const ext = extname2(path2).toLowerCase();
@@ -22178,6 +22720,7 @@ async function refToDataUri(path2) {
22178
22720
  }
22179
22721
 
22180
22722
  // ../../plugins/generations/image-gen/src/providers/seedream.ts
22723
+ init_bytes();
22181
22724
  import { Buffer as Buffer2 } from "buffer";
22182
22725
 
22183
22726
  // ../../plugins/generations/image-gen/src/providers/timeouts.ts
@@ -22376,9 +22919,11 @@ var createAgnes = (options) => {
22376
22919
  };
22377
22920
 
22378
22921
  // ../../plugins/generations/image-gen/src/providers/azure.ts
22922
+ init_bytes();
22379
22923
  import { extname as extname3 } from "path";
22380
22924
 
22381
22925
  // ../../plugins/generations/image-gen/src/providers/openai.ts
22926
+ init_bytes();
22382
22927
  import { Buffer as Buffer4 } from "buffer";
22383
22928
  import { basename as basename2 } from "path";
22384
22929
  var DEFAULT_MODEL3 = "gpt-image-2";
@@ -22788,6 +23333,7 @@ var createAzure = (options) => {
22788
23333
  };
22789
23334
 
22790
23335
  // ../../plugins/generations/image-gen/src/providers/codex-cli.ts
23336
+ init_bytes();
22791
23337
  import { spawn } from "child_process";
22792
23338
  import { randomBytes } from "crypto";
22793
23339
  import { access as access2, mkdir, rm, writeFile as writeFile2 } from "fs/promises";
@@ -23312,6 +23858,7 @@ import { execFileSync } from "child_process";
23312
23858
  import { randomUUID } from "crypto";
23313
23859
  import { tmpdir as tmpdir2 } from "os";
23314
23860
  import { join as join2 } from "path";
23861
+ init_bytes();
23315
23862
  var GOOGLE_MULTIMODAL_MODELS = [
23316
23863
  "gemini-3-pro-image",
23317
23864
  "gemini-3.1-flash-image",
@@ -23766,15 +24313,19 @@ var createJimeng = (options) => {
23766
24313
  };
23767
24314
 
23768
24315
  // ../../plugins/generations/image-gen/src/providers/minimax.ts
24316
+ init_bytes();
23769
24317
  import { Buffer as Buffer7 } from "buffer";
23770
24318
  var DEFAULT_MODEL9 = "image-01";
23771
24319
  var MAX_REFERENCE_IMAGE_BYTES = 10 * 1024 * 1024;
23772
24320
  var SUPPORTED_ASPECT_RATIOS = new Set(["1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]);
24321
+ function isAcceptablePixelSize(width, height) {
24322
+ return width >= 512 && width <= 2048 && height >= 512 && height <= 2048 && width % 8 === 0 && height % 8 === 0;
24323
+ }
23773
24324
  function validatePixelSize(width, height) {
23774
- if (width < 512 || width > 2048 || height < 512 || height > 2048) {
23775
- throw new Error("MiniMax custom size must keep width and height between 512 and 2048.");
23776
- }
23777
- if (width % 8 !== 0 || height % 8 !== 0) {
24325
+ if (!isAcceptablePixelSize(width, height)) {
24326
+ if (width < 512 || width > 2048 || height < 512 || height > 2048) {
24327
+ throw new Error("MiniMax custom size must keep width and height between 512 and 2048.");
24328
+ }
23778
24329
  throw new Error("MiniMax custom size requires width and height divisible by 8.");
23779
24330
  }
23780
24331
  }
@@ -23856,6 +24407,7 @@ var createMinimax = (options) => {
23856
24407
  defaultModel: () => DEFAULT_MODEL9,
23857
24408
  isAvailable: () => readEnv("MINIMAX_API_KEY") !== "",
23858
24409
  supportsReferenceImages: () => true,
24410
+ exactSize: (model, width, height) => model === "image-01" && isAcceptablePixelSize(width, height),
23859
24411
  validate(req) {
23860
24412
  requestBody(req.prompt, req.model, req);
23861
24413
  },
@@ -24896,6 +25448,65 @@ async function processGeneratorIO(inputPath, outputPath, options = {}) {
24896
25448
  }
24897
25449
  if (spec.job === "illustration")
24898
25450
  assertAnchorPlan(doc2?.body ?? "", positions, prompts.length);
25451
+ let _chromiumComposer;
25452
+ const resolveComposer = async () => {
25453
+ if (options.composer !== undefined)
25454
+ return options.composer;
25455
+ if (_chromiumComposer !== undefined)
25456
+ return _chromiumComposer;
25457
+ const { makeChromiumComposer: makeChromiumComposer2 } = await Promise.resolve().then(() => (init_chromium(), exports_chromium));
25458
+ _chromiumComposer = makeChromiumComposer2();
25459
+ return _chromiumComposer;
25460
+ };
25461
+ if (format.compose !== undefined) {
25462
+ const cfg = format.compose;
25463
+ if (spec.job !== "free") {
25464
+ throw new Error(`compose: format '${format.name}' has a compose block but the job is '${spec.job}' \u2014 compose formats require job: free`);
25465
+ }
25466
+ const vars = spec.vars ?? {};
25467
+ const headline = vars.headline ?? format.variables.headline?.default ?? "";
25468
+ if (!headline.trim()) {
25469
+ throw new Error(`compose: format '${format.name}' requires a non-empty vars.headline`);
25470
+ }
25471
+ const pointsRaw = vars.points ?? format.variables.points?.default ?? "";
25472
+ const points = pointsRaw.split(`
25473
+ `).filter((l) => l.trim() !== "");
25474
+ const promptVars = { subject: doc2?.body ?? "", ...spec.vars ?? {} };
25475
+ const textFreeSources = [
25476
+ { source: "the format template", text: format.template },
25477
+ {
25478
+ source: `the style preset '${style.name}'`,
25479
+ text: [style.modifiers, style.negative].filter((part) => part !== "").join(", ")
25480
+ },
25481
+ { source: "spec.extra", text: spec.extra ?? "" },
25482
+ ...Object.entries(promptVars).filter(([name]) => name !== "headline" && name !== "points" && name !== "index").map(([name, value]) => ({ source: `vars.${name}`, text: String(value) }))
25483
+ ];
25484
+ assertTextFreePrompt(prompts[0] ?? "", { headline, points }, textFreeSources);
25485
+ const fontsDir = join4(import.meta.dir, "..", "assets", "fonts");
25486
+ const fontNames = [cfg.fonts.headline, cfg.fonts.body];
25487
+ const fontBytesMap = new Map;
25488
+ for (const name of fontNames) {
25489
+ fontBytesMap.set(name, new Uint8Array(await readBytes(join4(fontsDir, name))));
25490
+ }
25491
+ const coverageMap = new Map;
25492
+ for (const [name, bytes] of fontBytesMap) {
25493
+ coverageMap.set(name, glyphCoverage(bytes));
25494
+ }
25495
+ if (format.width == null || format.height == null) {
25496
+ throw new Error(`image-gen: format '${format.name}' declares no canvas size; compose requires width and height`);
25497
+ }
25498
+ const cardInput = {
25499
+ headline,
25500
+ points,
25501
+ role: vars.role ?? format.variables.role?.default ?? "content",
25502
+ compose: cfg,
25503
+ width: format.width,
25504
+ height: format.height
25505
+ };
25506
+ assertCoverage(cardInput, coverageMap);
25507
+ const composer2 = await resolveComposer();
25508
+ await composer2.compose(cardInput);
25509
+ }
24899
25510
  const resolveProvider = options.providerResolver ?? defaultProviderResolver;
24900
25511
  const primary = resolveProvider(spec);
24901
25512
  const built = new Map;
@@ -24960,15 +25571,17 @@ async function processGeneratorIO(inputPath, outputPath, options = {}) {
24960
25571
  }
24961
25572
  const provider = at === 0 ? primary : createCandidate(name);
24962
25573
  const model = at === 0 ? spec.model ?? provider.defaultModel() : provider.defaultModel();
25574
+ const exact = format.width !== undefined && format.height !== undefined && provider.exactSize?.(model, format.width, format.height) === true;
24963
25575
  const request = {
24964
25576
  prompt,
24965
25577
  model,
24966
- aspectRatio: format.aspectRatio,
25578
+ ...exact ? { width: format.width, height: format.height } : { aspectRatio: format.aspectRatio },
24967
25579
  ...spec.referenceImages !== undefined && spec.referenceImages.length > 0 ? { referenceImages: spec.referenceImages } : {}
24968
25580
  };
24969
25581
  try {
24970
25582
  provider.validate?.(request);
24971
- return await provider.generate(request);
25583
+ const result = await provider.generate(request);
25584
+ return { result, sizeRequest: exact ? "exact" : "aspect" };
24972
25585
  } catch (err) {
24973
25586
  if (spec.fallback === undefined) {
24974
25587
  throw err;
@@ -24991,7 +25604,15 @@ async function processGeneratorIO(inputPath, outputPath, options = {}) {
24991
25604
  };
24992
25605
  let body = doc2?.body ?? "";
24993
25606
  for (const [index, prompt] of prompts.entries()) {
24994
- const result = await generateWithFallback(index + 1, prompt);
25607
+ const { result, sizeRequest } = await generateWithFallback(index + 1, prompt);
25608
+ const delivered = imageDimensions(result.bytes);
25609
+ if (format.width !== undefined && format.height !== undefined) {
25610
+ if (delivered === null) {
25611
+ echoError(`WARN image-gen: ${result.provider}/${result.model} delivered unparseable image bytes; dimensions omitted`);
25612
+ } else if (delivered.width !== format.width || delivered.height !== format.height) {
25613
+ echoError(`WARN image-gen: ${result.provider}/${result.model} delivered ${delivered.width}x${delivered.height}; format '${format.name}' declares ${format.width}x${format.height}`);
25614
+ }
25615
+ }
24995
25616
  let anchor;
24996
25617
  if (spec.job === "illustration") {
24997
25618
  anchor = positions?.[index];
@@ -25003,17 +25624,74 @@ async function processGeneratorIO(inputPath, outputPath, options = {}) {
25003
25624
  throw new Error(`Illustration anchor must occur exactly once in the source body, found ${parts.length - 1}: '${anchor.slice(0, 50)}'`);
25004
25625
  }
25005
25626
  }
25006
- const path2 = artifactPath(outputPath, stem, format.name, index + 1, result.extension);
25007
- await writeBytes(path2, result.bytes);
25008
- written.push(path2);
25627
+ let artifactPathValue;
25628
+ let artifactBytes;
25629
+ let artifactWidth;
25630
+ let artifactHeight;
25631
+ let illustrationMeta;
25632
+ if (format.compose !== undefined) {
25633
+ const illustrationArtifactPath = artifactPath(outputPath, stem, `${format.name}-illustration`, index + 1, result.extension);
25634
+ await writeBytes(illustrationArtifactPath, result.bytes);
25635
+ written.push(illustrationArtifactPath);
25636
+ const vars = spec.vars ?? {};
25637
+ const headline = vars.headline ?? format.variables.headline?.default ?? "";
25638
+ const pointsRaw = vars.points ?? format.variables.points?.default ?? "";
25639
+ const points = pointsRaw.split(`
25640
+ `).filter((l) => l.trim() !== "");
25641
+ if (format.width == null || format.height == null) {
25642
+ throw new Error(`image-gen: format '${format.name}' declares no canvas size; compose requires width and height`);
25643
+ }
25644
+ const cardInput = {
25645
+ headline,
25646
+ points,
25647
+ role: vars.role ?? format.variables.role?.default ?? "content",
25648
+ compose: format.compose,
25649
+ width: format.width,
25650
+ height: format.height,
25651
+ illustration: { bytes: result.bytes, mime: `image/${result.extension}` }
25652
+ };
25653
+ const composer2 = await resolveComposer();
25654
+ const cardBytes = await composer2.compose(cardInput);
25655
+ const cardPath = artifactPath(outputPath, stem, format.name, index + 1, "png");
25656
+ await writeBytes(cardPath, cardBytes);
25657
+ written.push(cardPath);
25658
+ const dims = imageDimensions(cardBytes);
25659
+ if (dims === null) {
25660
+ throw new Error(`compose: composed card dimensions are unparseable; format '${format.name}' requires ${format.width}x${format.height}`);
25661
+ }
25662
+ if (dims.width !== format.width || dims.height !== format.height) {
25663
+ throw new Error(`compose: card dimensions ${dims.width}x${dims.height} do not match format canvas ${format.width}x${format.height}`);
25664
+ }
25665
+ artifactPathValue = cardPath;
25666
+ artifactBytes = cardBytes.byteLength;
25667
+ artifactWidth = dims.width;
25668
+ artifactHeight = dims.height;
25669
+ illustrationMeta = {
25670
+ path: illustrationArtifactPath,
25671
+ bytes: result.bytes.byteLength,
25672
+ ...delivered !== null ? { width: delivered.width, height: delivered.height } : {}
25673
+ };
25674
+ } else {
25675
+ const path2 = artifactPath(outputPath, stem, format.name, index + 1, result.extension);
25676
+ await writeBytes(path2, result.bytes);
25677
+ written.push(path2);
25678
+ artifactPathValue = path2;
25679
+ artifactBytes = result.bytes.byteLength;
25680
+ artifactWidth = delivered?.width;
25681
+ artifactHeight = delivered?.height;
25682
+ }
25009
25683
  const artifact = {
25010
- path: path2,
25684
+ path: artifactPathValue,
25011
25685
  format: format.name,
25012
25686
  style: style.name,
25013
25687
  provider: result.provider,
25014
25688
  model: result.model,
25015
25689
  prompt,
25016
- bytes: result.bytes.byteLength
25690
+ bytes: artifactBytes,
25691
+ sizeRequest,
25692
+ ...artifactWidth !== undefined ? { width: artifactWidth } : {},
25693
+ ...artifactHeight !== undefined ? { height: artifactHeight } : {},
25694
+ ...illustrationMeta !== undefined ? { illustration: illustrationMeta } : {}
25017
25695
  };
25018
25696
  if (spec.seed !== undefined) {
25019
25697
  artifact.seed = spec.seed;
@@ -25024,10 +25702,10 @@ async function processGeneratorIO(inputPath, outputPath, options = {}) {
25024
25702
  const altText = `Illustration ${index + 1}`;
25025
25703
  const markdown = `${anchor}
25026
25704
 
25027
- ![${altText}](${path2})`;
25705
+ ![${altText}](${artifactPathValue})`;
25028
25706
  captions.push({
25029
25707
  index: index + 1,
25030
- path: path2,
25708
+ path: artifactPathValue,
25031
25709
  caption: anchor,
25032
25710
  altText,
25033
25711
  anchor