@taloswork/ose-shared 0.0.5 → 0.1.1
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/package.json +3 -3
- package/skills/document/SKILL.md +1 -1
- package/skills/document/bin/deck-build.cjs +0 -0
- package/skills/document/bin/deck-qa.cjs +0 -0
- package/skills/document/bin/doc-convert.cjs +277 -3
- package/skills/document/bin/doc-fill.cjs +0 -0
- package/skills/document/bin/doc-gen.cjs +307 -10
- package/skills/document/bin/doc-run.cjs +0 -0
- package/skills/document/bin/doc-scan.cjs +0 -0
- package/skills/document/bin/doc-text.cjs +0 -0
- package/skills/document/bin/doc-validate.cjs +0 -0
- package/skills/document/bin/doc-xml.cjs +0 -0
- package/skills/document/bin/docx-annotate.cjs +0 -0
- package/skills/document/bin/html2pptx.cjs +0 -0
- package/skills/document/bin/ocr-worker.cjs +0 -0
- package/skills/document/bin/ocr.cjs +0 -0
- package/skills/document/bin/pdf-form.cjs +0 -0
- package/skills/document/bin/pdf-images.cjs +0 -0
- package/skills/document/bin/pdf-ops.cjs +0 -0
- package/skills/document/bin/pdf-render.cjs +0 -0
- package/skills/document/bin/pdf-tables.cjs +0 -0
- package/skills/document/bin/pptx-clone.cjs +0 -0
- package/skills/document/bin/pptx-embed-fonts.cjs +0 -0
- package/skills/document/bin/pptx-set-image.cjs +0 -0
- package/skills/document/bin/pptx-slide.cjs +0 -0
- package/skills/document/bin/pptx-theme.cjs +0 -0
- package/skills/document/bin/pptx-tokenize.cjs +0 -0
- package/skills/document/bin/xlsx-query.cjs +0 -0
- package/skills/document/bin/xlsx-recalc.cjs +0 -0
- package/skills/document/rewrite.md +6 -0
- package/mcp/.gitkeep +0 -8
- package/skills/.gitkeep +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taloswork/ose-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Talos cross-OSE shared library — skills + MCP servers auto-loaded into every OSE (P93 2026-07-27).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
|
-
"_talosPublishHash": "sha256-
|
|
14
|
-
"_talosPublishedAt": "2026-08-
|
|
13
|
+
"_talosPublishHash": "sha256-qYMkSCYzW7s-GEXLCu9xYsIMRDV_FT9ZUxNJ4Dbkctk",
|
|
14
|
+
"_talosPublishedAt": "2026-08-28T05:55:50.205Z"
|
|
15
15
|
}
|
package/skills/document/SKILL.md
CHANGED
|
@@ -200,7 +200,7 @@ Fidelity-decision self-correct, but pointed at your OWN output:
|
|
|
200
200
|
- `bin/pdf-tables.cjs <file.pdf> [page] [minCols]` — recover text-layer tables as rows (ruled/scanned tables need render + vision instead).
|
|
201
201
|
- `bin/ocr.cjs <image.png|jpg> [lang]` — read text off a scanned image (tesseract.js). For a scanned PDF, `pdf-render` a page first, then OCR the PNG. Language data downloads on first use / set `$TALOS_TESSDATA` for offline. **Prefer vision** when a vision model is available; OCR is the text-only fallback.
|
|
202
202
|
- `bin/xlsx-query.cjs <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]]` — spreadsheet compute.
|
|
203
|
-
- `bin/doc-gen.cjs <docx|xlsx|pptx|pdf> <out> <spec.json> [--theme <theme.json>] [--topic <subject> | --palette <id>] [--design native|html]` — generate documents from scratch; `--theme` applies a presentation theme (colors/fonts/cover/header/footer/pptx master — see rewrite.md "Themes"; built-ins in `themes/`). **`--topic <subject>` (pptx, when no `--theme`) picks a topic-adaptive palette** so the deck is designed-for-subject instead of template-flat, and turns on richness (motif + gradient wash + depth). `--palette <id>` forces a specific palette (`doc-gen --list-palettes` prints them). **`--accent <#hex>` / `--style <skin>` (docx/pdf)**: white-paper-black-text base with ONE brand color on H1/H2 + a faint table-header tint — the practical doc shape (a document is read/printed/edited, not projected, so body text stays near-black). `--accent` takes a literal hex; `--style <skin>` pulls just the `--accent` from `styles/<skin>/skin.css` (the deck skin's full palette is NOT carried onto paper). Precedence: `--theme` > spec `"theme"` > `--accent`/`--style` > `--palette`/`--topic` > default. `--design html` (pptx only, in-app) renders eligible slides through designed HTML templates for a magazine look — richer but a flat collage (no master structure); auto-falls back to native when headless or a slide has no template. Default `native` (structured, editable, charts, headless-safe).
|
|
203
|
+
- `bin/doc-gen.cjs <docx|xlsx|pptx|pdf> <out> <spec.json> [--theme <theme.json>] [--topic <subject> | --palette <id>] [--design native|html]` — generate documents from scratch. DOCX/PDF body fields are exactly `paragraphs` and/or `blocks` (never `sections`); unknown fields and empty bodies fail with `DOC_SPEC_INVALID` before any output is written (`allowCoverOnly:true` is the explicit title-only opt-in). Correct the reported JSON path and rerun. `--theme` applies a presentation theme (colors/fonts/cover/header/footer/pptx master — see rewrite.md "Themes"; built-ins in `themes/`). **`--topic <subject>` (pptx, when no `--theme`) picks a topic-adaptive palette** so the deck is designed-for-subject instead of template-flat, and turns on richness (motif + gradient wash + depth). `--palette <id>` forces a specific palette (`doc-gen --list-palettes` prints them). **`--accent <#hex>` / `--style <skin>` (docx/pdf)**: white-paper-black-text base with ONE brand color on H1/H2 + a faint table-header tint — the practical doc shape (a document is read/printed/edited, not projected, so body text stays near-black). `--accent` takes a literal hex; `--style <skin>` pulls just the `--accent` from `styles/<skin>/skin.css` (the deck skin's full palette is NOT carried onto paper). Precedence: `--theme` > spec `"theme"` > `--accent`/`--style` > `--palette`/`--topic` > default. `--design html` (pptx only, in-app) renders eligible slides through designed HTML templates for a magazine look — richer but a flat collage (no master structure); auto-falls back to native when headless or a slide has no template. Default `native` (structured, editable, charts, headless-safe).
|
|
204
204
|
- `bin/doc-convert.cjs <in.docx|pptx|xlsx> <out.pdf>` — faithful docx/pptx/xlsx → PDF conversion. Engine precedence (best fidelity first, each downgrade tagged on stdout `[pdf (…)]` + warned on stderr): (1) **LibreOffice** if a system `soffice` is found — true layout render (page geometry, headers/footers, text-box positions, native charts), offline; (2) **LibreOffice sidecar** if `TALOS_OFFICE_URL` is set — same true-layout fidelity via a remote converter (e.g. Gotenberg), needs network; (3) **Chromium** (in-app) — reconstructed HTML, content/structure/images preserved but in-document layout is not (pptx = one page per slide); (4) **pdf-lib text-only** headless last resort — images/tables flatten. Surface the resulting `[pdf (engine)]` caveat to the user. NOTE: spreadsheets (xlsx/xls/ods) render ONLY via tiers 1–2 (real LibreOffice); tiers 3–4 do not rebuild spreadsheets, so with no LibreOffice available the command errors (never a degraded text-only sheet).
|
|
205
205
|
- `bin/doc-validate.cjs <file.docx|pptx|xlsx> [--strict] [--design-lint]` — structural OOXML validation gate (zip/parts/rels/Content_Types/XML well-formedness); run after every generation or edit; exit 1 = broken, fix before delivering. `--strict` adds full ECMA-376 schema (XSD) validation of every recognised part — REQUIRED after custom-code (doc-run/html2pptx) or raw-XML output; a strict failure means the file is non-standard even if Office opens it. `--design-lint` (pptx only) adds an ADVISORY design pass — flags `text-only-slide` (a wall of text with no visual anchor), `low-contrast-text`, `placeholder-text` (a layout pasted but never filled — 替换为…/占位/Lorem still in the slide), `font-mismatch` (CJK text set in a Latin-only font → 豆腐; expected language defaults to the system locale, override with `TALOS_DECK_LANG`), `off-canvas`, `tiny-font`, `shape-overlap`; findings are warnings (never change the exit code) but are real design bugs — fix them. See deck-design.md "Design principles".
|
|
206
206
|
- `bin/deck-qa.cjs <file.pptx> [--json]` — MECHANICAL design QA (deterministic, token-free): reads slide XML and flags `off-canvas` (content clipped past the edge), `tiny-font` (<10pt), `shape-overlap` (colliding text), plus `text-only-slide` / `low-contrast-text` / `placeholder-text` / `font-mismatch` (CJK text in a Latin-only font → 豆腐). Advisory (exit 0 even with findings). `--json` → `{ok,findings:[{slide,code,message}]}` for the visual-QA loop. It is the CHEAP GATE before spending vision tokens — run it every QA round and fix all findings first; then do the vision critic + per-slide regeneration (deck-design.md "Verify before delivering").
|
|
File without changes
|
|
File without changes
|
|
@@ -168597,12 +168597,285 @@ function iconSvgDataUri(name, colorHex) {
|
|
|
168597
168597
|
function bareHex(c) {
|
|
168598
168598
|
return c.replace("#", "").toUpperCase();
|
|
168599
168599
|
}
|
|
168600
|
+
var DocumentSpecValidationError = class extends Error {
|
|
168601
|
+
code = "DOC_SPEC_INVALID";
|
|
168602
|
+
issues;
|
|
168603
|
+
constructor(issues) {
|
|
168604
|
+
const detail = issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ");
|
|
168605
|
+
super(`DOC_SPEC_INVALID: ${detail}`);
|
|
168606
|
+
this.name = "DocumentSpecValidationError";
|
|
168607
|
+
this.issues = [...issues];
|
|
168608
|
+
}
|
|
168609
|
+
};
|
|
168610
|
+
var TEXT_DOCUMENT_KEYS = /* @__PURE__ */ new Set(["title", "paragraphs", "blocks", "allowCoverOnly", "fontPath", "theme", "topic", "palette"]);
|
|
168611
|
+
var XLSX_DOCUMENT_KEYS = /* @__PURE__ */ new Set(["sheets", "theme", "topic", "palette"]);
|
|
168612
|
+
var PPTX_DOCUMENT_KEYS = /* @__PURE__ */ new Set(["slides", "theme", "topic", "palette"]);
|
|
168613
|
+
var PPTX_SLIDE_KEYS = /* @__PURE__ */ new Set([
|
|
168614
|
+
"title",
|
|
168615
|
+
"cover",
|
|
168616
|
+
"toc",
|
|
168617
|
+
"ending",
|
|
168618
|
+
"section",
|
|
168619
|
+
"kpiCards",
|
|
168620
|
+
"cards",
|
|
168621
|
+
"timeline",
|
|
168622
|
+
"steps",
|
|
168623
|
+
"iconList",
|
|
168624
|
+
"splitImage",
|
|
168625
|
+
"comparison",
|
|
168626
|
+
"quadrant",
|
|
168627
|
+
"bigStat",
|
|
168628
|
+
"quote",
|
|
168629
|
+
"bullets",
|
|
168630
|
+
"table",
|
|
168631
|
+
"image",
|
|
168632
|
+
"chart"
|
|
168633
|
+
]);
|
|
168634
|
+
function isJsonRecord(value) {
|
|
168635
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
168636
|
+
}
|
|
168637
|
+
function addUnknownPropertyIssues(record, allowed, path, issues) {
|
|
168638
|
+
for (const key of Object.keys(record)) {
|
|
168639
|
+
if (allowed.has(key))
|
|
168640
|
+
continue;
|
|
168641
|
+
const message = allowed === TEXT_DOCUMENT_KEYS && (key === "section" || key === "sections") ? `${key} is unsupported for DOCX/PDF content; use $.blocks with heading/paragraph/list/table/image blocks (section is PPTX-only)` : `unsupported property ${JSON.stringify(key)}; allowed properties: ${[...allowed].join(", ")}`;
|
|
168642
|
+
issues.push({ path: `${path}.${key}`, code: "UNKNOWN_PROPERTY", message });
|
|
168643
|
+
}
|
|
168644
|
+
}
|
|
168645
|
+
function nonBlankString(value) {
|
|
168646
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
168647
|
+
}
|
|
168648
|
+
function validateTextBlock(value, path, issues) {
|
|
168649
|
+
if (!isJsonRecord(value)) {
|
|
168650
|
+
issues.push({ path, code: "INVALID_TYPE", message: "block must be an object" });
|
|
168651
|
+
return false;
|
|
168652
|
+
}
|
|
168653
|
+
if (typeof value.type !== "string") {
|
|
168654
|
+
issues.push({ path: `${path}.type`, code: "INVALID_TYPE", message: "block type must be a string" });
|
|
168655
|
+
return false;
|
|
168656
|
+
}
|
|
168657
|
+
const requireText = () => {
|
|
168658
|
+
if (nonBlankString(value.text))
|
|
168659
|
+
return true;
|
|
168660
|
+
issues.push({ path: `${path}.text`, code: "EMPTY_CONTENT", message: "text must be a non-empty string" });
|
|
168661
|
+
return false;
|
|
168662
|
+
};
|
|
168663
|
+
switch (value.type) {
|
|
168664
|
+
case "heading": {
|
|
168665
|
+
addUnknownPropertyIssues(value, /* @__PURE__ */ new Set(["type", "text", "level"]), path, issues);
|
|
168666
|
+
const valid = requireText();
|
|
168667
|
+
if (value.level !== void 0 && ![1, 2, 3, 4].includes(value.level)) {
|
|
168668
|
+
issues.push({ path: `${path}.level`, code: "INVALID_VALUE", message: "level must be one of 1, 2, 3, 4" });
|
|
168669
|
+
}
|
|
168670
|
+
return valid;
|
|
168671
|
+
}
|
|
168672
|
+
case "paragraph": {
|
|
168673
|
+
addUnknownPropertyIssues(value, /* @__PURE__ */ new Set(["type", "text", "bold", "italic"]), path, issues);
|
|
168674
|
+
const valid = requireText();
|
|
168675
|
+
for (const key of ["bold", "italic"]) {
|
|
168676
|
+
if (value[key] !== void 0 && typeof value[key] !== "boolean") {
|
|
168677
|
+
issues.push({ path: `${path}.${key}`, code: "INVALID_TYPE", message: `${key} must be a boolean` });
|
|
168678
|
+
}
|
|
168679
|
+
}
|
|
168680
|
+
return valid;
|
|
168681
|
+
}
|
|
168682
|
+
case "list": {
|
|
168683
|
+
addUnknownPropertyIssues(value, /* @__PURE__ */ new Set(["type", "items", "ordered"]), path, issues);
|
|
168684
|
+
if (!Array.isArray(value.items) || value.items.length === 0) {
|
|
168685
|
+
issues.push({ path: `${path}.items`, code: "EMPTY_CONTENT", message: "items must be a non-empty string array" });
|
|
168686
|
+
return false;
|
|
168687
|
+
}
|
|
168688
|
+
let valid = true;
|
|
168689
|
+
value.items.forEach((item, index) => {
|
|
168690
|
+
if (nonBlankString(item))
|
|
168691
|
+
return;
|
|
168692
|
+
valid = false;
|
|
168693
|
+
issues.push({ path: `${path}.items[${index}]`, code: "EMPTY_CONTENT", message: "list item must be a non-empty string" });
|
|
168694
|
+
});
|
|
168695
|
+
if (value.ordered !== void 0 && typeof value.ordered !== "boolean") {
|
|
168696
|
+
issues.push({ path: `${path}.ordered`, code: "INVALID_TYPE", message: "ordered must be a boolean" });
|
|
168697
|
+
}
|
|
168698
|
+
return valid;
|
|
168699
|
+
}
|
|
168700
|
+
case "table": {
|
|
168701
|
+
addUnknownPropertyIssues(value, /* @__PURE__ */ new Set(["type", "rows", "header", "columnWidths"]), path, issues);
|
|
168702
|
+
if (!Array.isArray(value.rows) || value.rows.length === 0) {
|
|
168703
|
+
issues.push({ path: `${path}.rows`, code: "EMPTY_CONTENT", message: "rows must be a non-empty array" });
|
|
168704
|
+
return false;
|
|
168705
|
+
}
|
|
168706
|
+
let hasCellContent = false;
|
|
168707
|
+
value.rows.forEach((row, rowIndex) => {
|
|
168708
|
+
if (!Array.isArray(row) || row.length === 0) {
|
|
168709
|
+
issues.push({ path: `${path}.rows[${rowIndex}]`, code: "INVALID_TYPE", message: "table row must be a non-empty array" });
|
|
168710
|
+
return;
|
|
168711
|
+
}
|
|
168712
|
+
row.forEach((cell, columnIndex) => {
|
|
168713
|
+
if (typeof cell !== "string" && typeof cell !== "number") {
|
|
168714
|
+
issues.push({ path: `${path}.rows[${rowIndex}][${columnIndex}]`, code: "INVALID_TYPE", message: "table cell must be a string or number" });
|
|
168715
|
+
} else if (String(cell).trim().length > 0) {
|
|
168716
|
+
hasCellContent = true;
|
|
168717
|
+
}
|
|
168718
|
+
});
|
|
168719
|
+
});
|
|
168720
|
+
if (!hasCellContent) {
|
|
168721
|
+
issues.push({ path: `${path}.rows`, code: "EMPTY_CONTENT", message: "table must contain at least one non-empty cell" });
|
|
168722
|
+
}
|
|
168723
|
+
if (value.header !== void 0 && typeof value.header !== "boolean") {
|
|
168724
|
+
issues.push({ path: `${path}.header`, code: "INVALID_TYPE", message: "header must be a boolean" });
|
|
168725
|
+
}
|
|
168726
|
+
if (value.columnWidths !== void 0 && (!Array.isArray(value.columnWidths) || value.columnWidths.some((width) => typeof width !== "number" || !Number.isFinite(width) || width <= 0))) {
|
|
168727
|
+
issues.push({ path: `${path}.columnWidths`, code: "INVALID_VALUE", message: "columnWidths must contain only positive finite numbers" });
|
|
168728
|
+
}
|
|
168729
|
+
return hasCellContent;
|
|
168730
|
+
}
|
|
168731
|
+
case "image": {
|
|
168732
|
+
addUnknownPropertyIssues(value, /* @__PURE__ */ new Set(["type", "data", "imageType", "width", "height"]), path, issues);
|
|
168733
|
+
if (!nonBlankString(value.data)) {
|
|
168734
|
+
issues.push({ path: `${path}.data`, code: "EMPTY_CONTENT", message: "image data must be non-empty base64" });
|
|
168735
|
+
return false;
|
|
168736
|
+
}
|
|
168737
|
+
if (value.imageType !== void 0 && !["png", "jpg", "gif", "bmp"].includes(String(value.imageType))) {
|
|
168738
|
+
issues.push({ path: `${path}.imageType`, code: "INVALID_VALUE", message: "imageType must be png, jpg, gif, or bmp" });
|
|
168739
|
+
}
|
|
168740
|
+
for (const key of ["width", "height"]) {
|
|
168741
|
+
if (value[key] !== void 0 && (typeof value[key] !== "number" || !Number.isFinite(value[key]) || value[key] <= 0)) {
|
|
168742
|
+
issues.push({ path: `${path}.${key}`, code: "INVALID_VALUE", message: `${key} must be a positive finite number` });
|
|
168743
|
+
}
|
|
168744
|
+
}
|
|
168745
|
+
return true;
|
|
168746
|
+
}
|
|
168747
|
+
default:
|
|
168748
|
+
issues.push({ path: `${path}.type`, code: "INVALID_VALUE", message: `unhandled block type: ${value.type}` });
|
|
168749
|
+
return false;
|
|
168750
|
+
}
|
|
168751
|
+
}
|
|
168752
|
+
function inspectTextDocumentSpec(spec) {
|
|
168753
|
+
const issues = [];
|
|
168754
|
+
if (!isJsonRecord(spec)) {
|
|
168755
|
+
issues.push({ path: "$", code: "INVALID_TYPE", message: "spec must be a JSON object" });
|
|
168756
|
+
return { issues, hasBody: false, hasTitle: false, allowCoverOnly: false };
|
|
168757
|
+
}
|
|
168758
|
+
addUnknownPropertyIssues(spec, TEXT_DOCUMENT_KEYS, "$", issues);
|
|
168759
|
+
const hasTitle = nonBlankString(spec.title);
|
|
168760
|
+
if (spec.title !== void 0 && !hasTitle) {
|
|
168761
|
+
issues.push({ path: "$.title", code: "EMPTY_CONTENT", message: "title must be a non-empty string when provided" });
|
|
168762
|
+
}
|
|
168763
|
+
if (spec.allowCoverOnly !== void 0 && typeof spec.allowCoverOnly !== "boolean") {
|
|
168764
|
+
issues.push({ path: "$.allowCoverOnly", code: "INVALID_TYPE", message: "allowCoverOnly must be a boolean" });
|
|
168765
|
+
}
|
|
168766
|
+
if (spec.fontPath !== void 0 && !nonBlankString(spec.fontPath)) {
|
|
168767
|
+
issues.push({ path: "$.fontPath", code: "INVALID_TYPE", message: "fontPath must be a non-empty string" });
|
|
168768
|
+
}
|
|
168769
|
+
let hasBody = false;
|
|
168770
|
+
if (spec.paragraphs !== void 0) {
|
|
168771
|
+
if (!Array.isArray(spec.paragraphs)) {
|
|
168772
|
+
issues.push({ path: "$.paragraphs", code: "INVALID_TYPE", message: "paragraphs must be a string array" });
|
|
168773
|
+
} else {
|
|
168774
|
+
spec.paragraphs.forEach((paragraph, index) => {
|
|
168775
|
+
if (nonBlankString(paragraph))
|
|
168776
|
+
hasBody = true;
|
|
168777
|
+
else
|
|
168778
|
+
issues.push({ path: `$.paragraphs[${index}]`, code: "EMPTY_CONTENT", message: "paragraph must be a non-empty string" });
|
|
168779
|
+
});
|
|
168780
|
+
}
|
|
168781
|
+
}
|
|
168782
|
+
if (spec.blocks !== void 0) {
|
|
168783
|
+
if (!Array.isArray(spec.blocks)) {
|
|
168784
|
+
issues.push({ path: "$.blocks", code: "INVALID_TYPE", message: "blocks must be an array" });
|
|
168785
|
+
} else {
|
|
168786
|
+
spec.blocks.forEach((block, index) => {
|
|
168787
|
+
if (validateTextBlock(block, `$.blocks[${index}]`, issues))
|
|
168788
|
+
hasBody = true;
|
|
168789
|
+
});
|
|
168790
|
+
}
|
|
168791
|
+
}
|
|
168792
|
+
return { issues, hasBody, hasTitle, allowCoverOnly: spec.allowCoverOnly === true };
|
|
168793
|
+
}
|
|
168600
168794
|
function assertTextDocumentSpec(spec) {
|
|
168601
|
-
|
|
168795
|
+
const { issues } = inspectTextDocumentSpec(spec);
|
|
168796
|
+
if (issues.length > 0)
|
|
168797
|
+
throw new DocumentSpecValidationError(issues);
|
|
168798
|
+
}
|
|
168799
|
+
function validateRows(rows, path, issues) {
|
|
168800
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
168801
|
+
issues.push({ path, code: "EMPTY_CONTENT", message: "rows must be a non-empty array" });
|
|
168802
|
+
return false;
|
|
168803
|
+
}
|
|
168804
|
+
let hasContent = false;
|
|
168805
|
+
rows.forEach((row, rowIndex) => {
|
|
168806
|
+
if (!Array.isArray(row) || row.length === 0) {
|
|
168807
|
+
issues.push({ path: `${path}[${rowIndex}]`, code: "INVALID_TYPE", message: "row must be a non-empty array" });
|
|
168808
|
+
return;
|
|
168809
|
+
}
|
|
168810
|
+
row.forEach((cell, columnIndex) => {
|
|
168811
|
+
if (typeof cell !== "string" && typeof cell !== "number") {
|
|
168812
|
+
issues.push({ path: `${path}[${rowIndex}][${columnIndex}]`, code: "INVALID_TYPE", message: "cell must be a string or number" });
|
|
168813
|
+
} else if (String(cell).trim().length > 0) {
|
|
168814
|
+
hasContent = true;
|
|
168815
|
+
}
|
|
168816
|
+
});
|
|
168817
|
+
});
|
|
168818
|
+
if (!hasContent)
|
|
168819
|
+
issues.push({ path, code: "EMPTY_CONTENT", message: "rows must contain at least one non-empty cell" });
|
|
168820
|
+
return hasContent;
|
|
168821
|
+
}
|
|
168822
|
+
function assertDocumentSpec(kind, spec) {
|
|
168823
|
+
if (kind === "docx" || kind === "pdf") {
|
|
168824
|
+
const inspection = inspectTextDocumentSpec(spec);
|
|
168825
|
+
if (!inspection.hasBody && !(inspection.allowCoverOnly && inspection.hasTitle)) {
|
|
168826
|
+
inspection.issues.push({
|
|
168827
|
+
path: "$",
|
|
168828
|
+
code: "EMPTY_CONTENT",
|
|
168829
|
+
message: "document body is empty; provide non-empty paragraphs/blocks, or set allowCoverOnly=true with a non-empty title"
|
|
168830
|
+
});
|
|
168831
|
+
}
|
|
168832
|
+
if (inspection.issues.length > 0)
|
|
168833
|
+
throw new DocumentSpecValidationError(inspection.issues);
|
|
168602
168834
|
return;
|
|
168603
|
-
if (Object.prototype.hasOwnProperty.call(spec, "section")) {
|
|
168604
|
-
throw new Error("unsupported document field: section (use blocks for DOCX/PDF content; section is PPTX-only)");
|
|
168605
168835
|
}
|
|
168836
|
+
const issues = [];
|
|
168837
|
+
if (!isJsonRecord(spec)) {
|
|
168838
|
+
throw new DocumentSpecValidationError([{ path: "$", code: "INVALID_TYPE", message: "spec must be a JSON object" }]);
|
|
168839
|
+
}
|
|
168840
|
+
if (kind === "xlsx") {
|
|
168841
|
+
addUnknownPropertyIssues(spec, XLSX_DOCUMENT_KEYS, "$", issues);
|
|
168842
|
+
if (!isJsonRecord(spec.sheets) || Object.keys(spec.sheets).length === 0) {
|
|
168843
|
+
issues.push({ path: "$.sheets", code: "EMPTY_CONTENT", message: "sheets must be a non-empty object" });
|
|
168844
|
+
} else {
|
|
168845
|
+
for (const [name, sheet] of Object.entries(spec.sheets)) {
|
|
168846
|
+
const path = `$.sheets[${JSON.stringify(name)}]`;
|
|
168847
|
+
if (Array.isArray(sheet)) {
|
|
168848
|
+
validateRows(sheet, path, issues);
|
|
168849
|
+
} else if (isJsonRecord(sheet)) {
|
|
168850
|
+
addUnknownPropertyIssues(sheet, /* @__PURE__ */ new Set(["rows", "header", "columnWidths", "numFmt"]), path, issues);
|
|
168851
|
+
validateRows(sheet.rows, `${path}.rows`, issues);
|
|
168852
|
+
} else {
|
|
168853
|
+
issues.push({ path, code: "INVALID_TYPE", message: "sheet must be a row array or an object containing rows" });
|
|
168854
|
+
}
|
|
168855
|
+
}
|
|
168856
|
+
}
|
|
168857
|
+
} else if (kind === "pptx") {
|
|
168858
|
+
addUnknownPropertyIssues(spec, PPTX_DOCUMENT_KEYS, "$", issues);
|
|
168859
|
+
if (!Array.isArray(spec.slides) || spec.slides.length === 0) {
|
|
168860
|
+
issues.push({ path: "$.slides", code: "EMPTY_CONTENT", message: "slides must be a non-empty array" });
|
|
168861
|
+
} else {
|
|
168862
|
+
spec.slides.forEach((slide, index) => {
|
|
168863
|
+
const path = `$.slides[${index}]`;
|
|
168864
|
+
if (!isJsonRecord(slide)) {
|
|
168865
|
+
issues.push({ path, code: "INVALID_TYPE", message: "slide must be an object" });
|
|
168866
|
+
return;
|
|
168867
|
+
}
|
|
168868
|
+
addUnknownPropertyIssues(slide, PPTX_SLIDE_KEYS, path, issues);
|
|
168869
|
+
if (Object.keys(slide).length === 0) {
|
|
168870
|
+
issues.push({ path, code: "EMPTY_CONTENT", message: "slide must contain at least one supported field" });
|
|
168871
|
+
}
|
|
168872
|
+
});
|
|
168873
|
+
}
|
|
168874
|
+
} else {
|
|
168875
|
+
throw new Error(`unsupported kind: ${String(kind)}`);
|
|
168876
|
+
}
|
|
168877
|
+
if (issues.length > 0)
|
|
168878
|
+
throw new DocumentSpecValidationError(issues);
|
|
168606
168879
|
}
|
|
168607
168880
|
var HEADINGS = [HeadingLevel.HEADING_1, HeadingLevel.HEADING_2, HeadingLevel.HEADING_3, HeadingLevel.HEADING_4];
|
|
168608
168881
|
var ORDERED_REF = "ol";
|
|
@@ -169864,6 +170137,7 @@ async function generatePdf(spec, themeInput) {
|
|
|
169864
170137
|
return doc.save();
|
|
169865
170138
|
}
|
|
169866
170139
|
async function generateDocument(kind, spec, theme) {
|
|
170140
|
+
assertDocumentSpec(kind, spec);
|
|
169867
170141
|
switch (kind) {
|
|
169868
170142
|
case "docx":
|
|
169869
170143
|
return generateDocx(spec, theme);
|
|
File without changes
|
|
@@ -36477,7 +36477,7 @@ var require_polyfills = __commonJS({
|
|
|
36477
36477
|
}
|
|
36478
36478
|
if (platform === "win32") {
|
|
36479
36479
|
fs.rename = typeof fs.rename !== "function" ? fs.rename : (function(fs$rename) {
|
|
36480
|
-
function
|
|
36480
|
+
function rename2(from, to, cb) {
|
|
36481
36481
|
var start = Date.now();
|
|
36482
36482
|
var backoff = 0;
|
|
36483
36483
|
fs$rename(from, to, function CB(er) {
|
|
@@ -36497,8 +36497,8 @@ var require_polyfills = __commonJS({
|
|
|
36497
36497
|
if (cb) cb(er);
|
|
36498
36498
|
});
|
|
36499
36499
|
}
|
|
36500
|
-
if (Object.setPrototypeOf) Object.setPrototypeOf(
|
|
36501
|
-
return
|
|
36500
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
|
|
36501
|
+
return rename2;
|
|
36502
36502
|
})(fs.rename);
|
|
36503
36503
|
}
|
|
36504
36504
|
fs.read = typeof fs.read !== "function" ? fs.read : (function(fs$read) {
|
|
@@ -72021,8 +72021,8 @@ var require_tmp = __commonJS({
|
|
|
72021
72021
|
if (!_isUndefined(options.name)) {
|
|
72022
72022
|
const name = options.name;
|
|
72023
72023
|
if (path.isAbsolute(name)) throw new Error(`name option must not contain an absolute path, found "${name}".`);
|
|
72024
|
-
const
|
|
72025
|
-
if (
|
|
72024
|
+
const basename2 = path.basename(name);
|
|
72025
|
+
if (basename2 === ".." || basename2 === "." || basename2 !== name) {
|
|
72026
72026
|
throw new Error(`name option must not contain a path, found "${name}".`);
|
|
72027
72027
|
}
|
|
72028
72028
|
}
|
|
@@ -152474,12 +152474,285 @@ function iconSvgDataUri(name, colorHex) {
|
|
|
152474
152474
|
function bareHex(c) {
|
|
152475
152475
|
return c.replace("#", "").toUpperCase();
|
|
152476
152476
|
}
|
|
152477
|
+
var DocumentSpecValidationError = class extends Error {
|
|
152478
|
+
code = "DOC_SPEC_INVALID";
|
|
152479
|
+
issues;
|
|
152480
|
+
constructor(issues) {
|
|
152481
|
+
const detail = issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ");
|
|
152482
|
+
super(`DOC_SPEC_INVALID: ${detail}`);
|
|
152483
|
+
this.name = "DocumentSpecValidationError";
|
|
152484
|
+
this.issues = [...issues];
|
|
152485
|
+
}
|
|
152486
|
+
};
|
|
152487
|
+
var TEXT_DOCUMENT_KEYS = /* @__PURE__ */ new Set(["title", "paragraphs", "blocks", "allowCoverOnly", "fontPath", "theme", "topic", "palette"]);
|
|
152488
|
+
var XLSX_DOCUMENT_KEYS = /* @__PURE__ */ new Set(["sheets", "theme", "topic", "palette"]);
|
|
152489
|
+
var PPTX_DOCUMENT_KEYS = /* @__PURE__ */ new Set(["slides", "theme", "topic", "palette"]);
|
|
152490
|
+
var PPTX_SLIDE_KEYS = /* @__PURE__ */ new Set([
|
|
152491
|
+
"title",
|
|
152492
|
+
"cover",
|
|
152493
|
+
"toc",
|
|
152494
|
+
"ending",
|
|
152495
|
+
"section",
|
|
152496
|
+
"kpiCards",
|
|
152497
|
+
"cards",
|
|
152498
|
+
"timeline",
|
|
152499
|
+
"steps",
|
|
152500
|
+
"iconList",
|
|
152501
|
+
"splitImage",
|
|
152502
|
+
"comparison",
|
|
152503
|
+
"quadrant",
|
|
152504
|
+
"bigStat",
|
|
152505
|
+
"quote",
|
|
152506
|
+
"bullets",
|
|
152507
|
+
"table",
|
|
152508
|
+
"image",
|
|
152509
|
+
"chart"
|
|
152510
|
+
]);
|
|
152511
|
+
function isJsonRecord(value) {
|
|
152512
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
152513
|
+
}
|
|
152514
|
+
function addUnknownPropertyIssues(record, allowed, path, issues) {
|
|
152515
|
+
for (const key of Object.keys(record)) {
|
|
152516
|
+
if (allowed.has(key))
|
|
152517
|
+
continue;
|
|
152518
|
+
const message = allowed === TEXT_DOCUMENT_KEYS && (key === "section" || key === "sections") ? `${key} is unsupported for DOCX/PDF content; use $.blocks with heading/paragraph/list/table/image blocks (section is PPTX-only)` : `unsupported property ${JSON.stringify(key)}; allowed properties: ${[...allowed].join(", ")}`;
|
|
152519
|
+
issues.push({ path: `${path}.${key}`, code: "UNKNOWN_PROPERTY", message });
|
|
152520
|
+
}
|
|
152521
|
+
}
|
|
152522
|
+
function nonBlankString(value) {
|
|
152523
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
152524
|
+
}
|
|
152525
|
+
function validateTextBlock(value, path, issues) {
|
|
152526
|
+
if (!isJsonRecord(value)) {
|
|
152527
|
+
issues.push({ path, code: "INVALID_TYPE", message: "block must be an object" });
|
|
152528
|
+
return false;
|
|
152529
|
+
}
|
|
152530
|
+
if (typeof value.type !== "string") {
|
|
152531
|
+
issues.push({ path: `${path}.type`, code: "INVALID_TYPE", message: "block type must be a string" });
|
|
152532
|
+
return false;
|
|
152533
|
+
}
|
|
152534
|
+
const requireText = () => {
|
|
152535
|
+
if (nonBlankString(value.text))
|
|
152536
|
+
return true;
|
|
152537
|
+
issues.push({ path: `${path}.text`, code: "EMPTY_CONTENT", message: "text must be a non-empty string" });
|
|
152538
|
+
return false;
|
|
152539
|
+
};
|
|
152540
|
+
switch (value.type) {
|
|
152541
|
+
case "heading": {
|
|
152542
|
+
addUnknownPropertyIssues(value, /* @__PURE__ */ new Set(["type", "text", "level"]), path, issues);
|
|
152543
|
+
const valid = requireText();
|
|
152544
|
+
if (value.level !== void 0 && ![1, 2, 3, 4].includes(value.level)) {
|
|
152545
|
+
issues.push({ path: `${path}.level`, code: "INVALID_VALUE", message: "level must be one of 1, 2, 3, 4" });
|
|
152546
|
+
}
|
|
152547
|
+
return valid;
|
|
152548
|
+
}
|
|
152549
|
+
case "paragraph": {
|
|
152550
|
+
addUnknownPropertyIssues(value, /* @__PURE__ */ new Set(["type", "text", "bold", "italic"]), path, issues);
|
|
152551
|
+
const valid = requireText();
|
|
152552
|
+
for (const key of ["bold", "italic"]) {
|
|
152553
|
+
if (value[key] !== void 0 && typeof value[key] !== "boolean") {
|
|
152554
|
+
issues.push({ path: `${path}.${key}`, code: "INVALID_TYPE", message: `${key} must be a boolean` });
|
|
152555
|
+
}
|
|
152556
|
+
}
|
|
152557
|
+
return valid;
|
|
152558
|
+
}
|
|
152559
|
+
case "list": {
|
|
152560
|
+
addUnknownPropertyIssues(value, /* @__PURE__ */ new Set(["type", "items", "ordered"]), path, issues);
|
|
152561
|
+
if (!Array.isArray(value.items) || value.items.length === 0) {
|
|
152562
|
+
issues.push({ path: `${path}.items`, code: "EMPTY_CONTENT", message: "items must be a non-empty string array" });
|
|
152563
|
+
return false;
|
|
152564
|
+
}
|
|
152565
|
+
let valid = true;
|
|
152566
|
+
value.items.forEach((item, index) => {
|
|
152567
|
+
if (nonBlankString(item))
|
|
152568
|
+
return;
|
|
152569
|
+
valid = false;
|
|
152570
|
+
issues.push({ path: `${path}.items[${index}]`, code: "EMPTY_CONTENT", message: "list item must be a non-empty string" });
|
|
152571
|
+
});
|
|
152572
|
+
if (value.ordered !== void 0 && typeof value.ordered !== "boolean") {
|
|
152573
|
+
issues.push({ path: `${path}.ordered`, code: "INVALID_TYPE", message: "ordered must be a boolean" });
|
|
152574
|
+
}
|
|
152575
|
+
return valid;
|
|
152576
|
+
}
|
|
152577
|
+
case "table": {
|
|
152578
|
+
addUnknownPropertyIssues(value, /* @__PURE__ */ new Set(["type", "rows", "header", "columnWidths"]), path, issues);
|
|
152579
|
+
if (!Array.isArray(value.rows) || value.rows.length === 0) {
|
|
152580
|
+
issues.push({ path: `${path}.rows`, code: "EMPTY_CONTENT", message: "rows must be a non-empty array" });
|
|
152581
|
+
return false;
|
|
152582
|
+
}
|
|
152583
|
+
let hasCellContent = false;
|
|
152584
|
+
value.rows.forEach((row, rowIndex) => {
|
|
152585
|
+
if (!Array.isArray(row) || row.length === 0) {
|
|
152586
|
+
issues.push({ path: `${path}.rows[${rowIndex}]`, code: "INVALID_TYPE", message: "table row must be a non-empty array" });
|
|
152587
|
+
return;
|
|
152588
|
+
}
|
|
152589
|
+
row.forEach((cell, columnIndex) => {
|
|
152590
|
+
if (typeof cell !== "string" && typeof cell !== "number") {
|
|
152591
|
+
issues.push({ path: `${path}.rows[${rowIndex}][${columnIndex}]`, code: "INVALID_TYPE", message: "table cell must be a string or number" });
|
|
152592
|
+
} else if (String(cell).trim().length > 0) {
|
|
152593
|
+
hasCellContent = true;
|
|
152594
|
+
}
|
|
152595
|
+
});
|
|
152596
|
+
});
|
|
152597
|
+
if (!hasCellContent) {
|
|
152598
|
+
issues.push({ path: `${path}.rows`, code: "EMPTY_CONTENT", message: "table must contain at least one non-empty cell" });
|
|
152599
|
+
}
|
|
152600
|
+
if (value.header !== void 0 && typeof value.header !== "boolean") {
|
|
152601
|
+
issues.push({ path: `${path}.header`, code: "INVALID_TYPE", message: "header must be a boolean" });
|
|
152602
|
+
}
|
|
152603
|
+
if (value.columnWidths !== void 0 && (!Array.isArray(value.columnWidths) || value.columnWidths.some((width) => typeof width !== "number" || !Number.isFinite(width) || width <= 0))) {
|
|
152604
|
+
issues.push({ path: `${path}.columnWidths`, code: "INVALID_VALUE", message: "columnWidths must contain only positive finite numbers" });
|
|
152605
|
+
}
|
|
152606
|
+
return hasCellContent;
|
|
152607
|
+
}
|
|
152608
|
+
case "image": {
|
|
152609
|
+
addUnknownPropertyIssues(value, /* @__PURE__ */ new Set(["type", "data", "imageType", "width", "height"]), path, issues);
|
|
152610
|
+
if (!nonBlankString(value.data)) {
|
|
152611
|
+
issues.push({ path: `${path}.data`, code: "EMPTY_CONTENT", message: "image data must be non-empty base64" });
|
|
152612
|
+
return false;
|
|
152613
|
+
}
|
|
152614
|
+
if (value.imageType !== void 0 && !["png", "jpg", "gif", "bmp"].includes(String(value.imageType))) {
|
|
152615
|
+
issues.push({ path: `${path}.imageType`, code: "INVALID_VALUE", message: "imageType must be png, jpg, gif, or bmp" });
|
|
152616
|
+
}
|
|
152617
|
+
for (const key of ["width", "height"]) {
|
|
152618
|
+
if (value[key] !== void 0 && (typeof value[key] !== "number" || !Number.isFinite(value[key]) || value[key] <= 0)) {
|
|
152619
|
+
issues.push({ path: `${path}.${key}`, code: "INVALID_VALUE", message: `${key} must be a positive finite number` });
|
|
152620
|
+
}
|
|
152621
|
+
}
|
|
152622
|
+
return true;
|
|
152623
|
+
}
|
|
152624
|
+
default:
|
|
152625
|
+
issues.push({ path: `${path}.type`, code: "INVALID_VALUE", message: `unhandled block type: ${value.type}` });
|
|
152626
|
+
return false;
|
|
152627
|
+
}
|
|
152628
|
+
}
|
|
152629
|
+
function inspectTextDocumentSpec(spec) {
|
|
152630
|
+
const issues = [];
|
|
152631
|
+
if (!isJsonRecord(spec)) {
|
|
152632
|
+
issues.push({ path: "$", code: "INVALID_TYPE", message: "spec must be a JSON object" });
|
|
152633
|
+
return { issues, hasBody: false, hasTitle: false, allowCoverOnly: false };
|
|
152634
|
+
}
|
|
152635
|
+
addUnknownPropertyIssues(spec, TEXT_DOCUMENT_KEYS, "$", issues);
|
|
152636
|
+
const hasTitle = nonBlankString(spec.title);
|
|
152637
|
+
if (spec.title !== void 0 && !hasTitle) {
|
|
152638
|
+
issues.push({ path: "$.title", code: "EMPTY_CONTENT", message: "title must be a non-empty string when provided" });
|
|
152639
|
+
}
|
|
152640
|
+
if (spec.allowCoverOnly !== void 0 && typeof spec.allowCoverOnly !== "boolean") {
|
|
152641
|
+
issues.push({ path: "$.allowCoverOnly", code: "INVALID_TYPE", message: "allowCoverOnly must be a boolean" });
|
|
152642
|
+
}
|
|
152643
|
+
if (spec.fontPath !== void 0 && !nonBlankString(spec.fontPath)) {
|
|
152644
|
+
issues.push({ path: "$.fontPath", code: "INVALID_TYPE", message: "fontPath must be a non-empty string" });
|
|
152645
|
+
}
|
|
152646
|
+
let hasBody = false;
|
|
152647
|
+
if (spec.paragraphs !== void 0) {
|
|
152648
|
+
if (!Array.isArray(spec.paragraphs)) {
|
|
152649
|
+
issues.push({ path: "$.paragraphs", code: "INVALID_TYPE", message: "paragraphs must be a string array" });
|
|
152650
|
+
} else {
|
|
152651
|
+
spec.paragraphs.forEach((paragraph, index) => {
|
|
152652
|
+
if (nonBlankString(paragraph))
|
|
152653
|
+
hasBody = true;
|
|
152654
|
+
else
|
|
152655
|
+
issues.push({ path: `$.paragraphs[${index}]`, code: "EMPTY_CONTENT", message: "paragraph must be a non-empty string" });
|
|
152656
|
+
});
|
|
152657
|
+
}
|
|
152658
|
+
}
|
|
152659
|
+
if (spec.blocks !== void 0) {
|
|
152660
|
+
if (!Array.isArray(spec.blocks)) {
|
|
152661
|
+
issues.push({ path: "$.blocks", code: "INVALID_TYPE", message: "blocks must be an array" });
|
|
152662
|
+
} else {
|
|
152663
|
+
spec.blocks.forEach((block, index) => {
|
|
152664
|
+
if (validateTextBlock(block, `$.blocks[${index}]`, issues))
|
|
152665
|
+
hasBody = true;
|
|
152666
|
+
});
|
|
152667
|
+
}
|
|
152668
|
+
}
|
|
152669
|
+
return { issues, hasBody, hasTitle, allowCoverOnly: spec.allowCoverOnly === true };
|
|
152670
|
+
}
|
|
152477
152671
|
function assertTextDocumentSpec(spec) {
|
|
152478
|
-
|
|
152672
|
+
const { issues } = inspectTextDocumentSpec(spec);
|
|
152673
|
+
if (issues.length > 0)
|
|
152674
|
+
throw new DocumentSpecValidationError(issues);
|
|
152675
|
+
}
|
|
152676
|
+
function validateRows(rows, path, issues) {
|
|
152677
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
152678
|
+
issues.push({ path, code: "EMPTY_CONTENT", message: "rows must be a non-empty array" });
|
|
152679
|
+
return false;
|
|
152680
|
+
}
|
|
152681
|
+
let hasContent = false;
|
|
152682
|
+
rows.forEach((row, rowIndex) => {
|
|
152683
|
+
if (!Array.isArray(row) || row.length === 0) {
|
|
152684
|
+
issues.push({ path: `${path}[${rowIndex}]`, code: "INVALID_TYPE", message: "row must be a non-empty array" });
|
|
152685
|
+
return;
|
|
152686
|
+
}
|
|
152687
|
+
row.forEach((cell, columnIndex) => {
|
|
152688
|
+
if (typeof cell !== "string" && typeof cell !== "number") {
|
|
152689
|
+
issues.push({ path: `${path}[${rowIndex}][${columnIndex}]`, code: "INVALID_TYPE", message: "cell must be a string or number" });
|
|
152690
|
+
} else if (String(cell).trim().length > 0) {
|
|
152691
|
+
hasContent = true;
|
|
152692
|
+
}
|
|
152693
|
+
});
|
|
152694
|
+
});
|
|
152695
|
+
if (!hasContent)
|
|
152696
|
+
issues.push({ path, code: "EMPTY_CONTENT", message: "rows must contain at least one non-empty cell" });
|
|
152697
|
+
return hasContent;
|
|
152698
|
+
}
|
|
152699
|
+
function assertDocumentSpec(kind, spec) {
|
|
152700
|
+
if (kind === "docx" || kind === "pdf") {
|
|
152701
|
+
const inspection = inspectTextDocumentSpec(spec);
|
|
152702
|
+
if (!inspection.hasBody && !(inspection.allowCoverOnly && inspection.hasTitle)) {
|
|
152703
|
+
inspection.issues.push({
|
|
152704
|
+
path: "$",
|
|
152705
|
+
code: "EMPTY_CONTENT",
|
|
152706
|
+
message: "document body is empty; provide non-empty paragraphs/blocks, or set allowCoverOnly=true with a non-empty title"
|
|
152707
|
+
});
|
|
152708
|
+
}
|
|
152709
|
+
if (inspection.issues.length > 0)
|
|
152710
|
+
throw new DocumentSpecValidationError(inspection.issues);
|
|
152479
152711
|
return;
|
|
152480
|
-
if (Object.prototype.hasOwnProperty.call(spec, "section")) {
|
|
152481
|
-
throw new Error("unsupported document field: section (use blocks for DOCX/PDF content; section is PPTX-only)");
|
|
152482
152712
|
}
|
|
152713
|
+
const issues = [];
|
|
152714
|
+
if (!isJsonRecord(spec)) {
|
|
152715
|
+
throw new DocumentSpecValidationError([{ path: "$", code: "INVALID_TYPE", message: "spec must be a JSON object" }]);
|
|
152716
|
+
}
|
|
152717
|
+
if (kind === "xlsx") {
|
|
152718
|
+
addUnknownPropertyIssues(spec, XLSX_DOCUMENT_KEYS, "$", issues);
|
|
152719
|
+
if (!isJsonRecord(spec.sheets) || Object.keys(spec.sheets).length === 0) {
|
|
152720
|
+
issues.push({ path: "$.sheets", code: "EMPTY_CONTENT", message: "sheets must be a non-empty object" });
|
|
152721
|
+
} else {
|
|
152722
|
+
for (const [name, sheet] of Object.entries(spec.sheets)) {
|
|
152723
|
+
const path = `$.sheets[${JSON.stringify(name)}]`;
|
|
152724
|
+
if (Array.isArray(sheet)) {
|
|
152725
|
+
validateRows(sheet, path, issues);
|
|
152726
|
+
} else if (isJsonRecord(sheet)) {
|
|
152727
|
+
addUnknownPropertyIssues(sheet, /* @__PURE__ */ new Set(["rows", "header", "columnWidths", "numFmt"]), path, issues);
|
|
152728
|
+
validateRows(sheet.rows, `${path}.rows`, issues);
|
|
152729
|
+
} else {
|
|
152730
|
+
issues.push({ path, code: "INVALID_TYPE", message: "sheet must be a row array or an object containing rows" });
|
|
152731
|
+
}
|
|
152732
|
+
}
|
|
152733
|
+
}
|
|
152734
|
+
} else if (kind === "pptx") {
|
|
152735
|
+
addUnknownPropertyIssues(spec, PPTX_DOCUMENT_KEYS, "$", issues);
|
|
152736
|
+
if (!Array.isArray(spec.slides) || spec.slides.length === 0) {
|
|
152737
|
+
issues.push({ path: "$.slides", code: "EMPTY_CONTENT", message: "slides must be a non-empty array" });
|
|
152738
|
+
} else {
|
|
152739
|
+
spec.slides.forEach((slide, index) => {
|
|
152740
|
+
const path = `$.slides[${index}]`;
|
|
152741
|
+
if (!isJsonRecord(slide)) {
|
|
152742
|
+
issues.push({ path, code: "INVALID_TYPE", message: "slide must be an object" });
|
|
152743
|
+
return;
|
|
152744
|
+
}
|
|
152745
|
+
addUnknownPropertyIssues(slide, PPTX_SLIDE_KEYS, path, issues);
|
|
152746
|
+
if (Object.keys(slide).length === 0) {
|
|
152747
|
+
issues.push({ path, code: "EMPTY_CONTENT", message: "slide must contain at least one supported field" });
|
|
152748
|
+
}
|
|
152749
|
+
});
|
|
152750
|
+
}
|
|
152751
|
+
} else {
|
|
152752
|
+
throw new Error(`unsupported kind: ${String(kind)}`);
|
|
152753
|
+
}
|
|
152754
|
+
if (issues.length > 0)
|
|
152755
|
+
throw new DocumentSpecValidationError(issues);
|
|
152483
152756
|
}
|
|
152484
152757
|
var HEADINGS = [HeadingLevel.HEADING_1, HeadingLevel.HEADING_2, HeadingLevel.HEADING_3, HeadingLevel.HEADING_4];
|
|
152485
152758
|
var ORDERED_REF = "ol";
|
|
@@ -153741,6 +154014,7 @@ async function generatePdf(spec, themeInput) {
|
|
|
153741
154014
|
return doc.save();
|
|
153742
154015
|
}
|
|
153743
154016
|
async function generateDocument(kind, spec, theme) {
|
|
154017
|
+
assertDocumentSpec(kind, spec);
|
|
153744
154018
|
switch (kind) {
|
|
153745
154019
|
case "docx":
|
|
153746
154020
|
return generateDocx(spec, theme);
|
|
@@ -153856,6 +154130,7 @@ function themePrintOptions(themeInput, title) {
|
|
|
153856
154130
|
// apps/admin/data/skills.lib/document/bin-src/doc-gen.mjs
|
|
153857
154131
|
var import_node_path = require("node:path");
|
|
153858
154132
|
var import_node_fs2 = require("node:fs");
|
|
154133
|
+
var import_node_crypto = require("node:crypto");
|
|
153859
154134
|
|
|
153860
154135
|
// packages/core/dist/fs/tpl-interp.js
|
|
153861
154136
|
var TAG = /\{\{\{?\s*([#/]?)\s*([^}]*?)\s*\}?\}\}/g;
|
|
@@ -154596,6 +154871,21 @@ function die(msg, code = 1) {
|
|
|
154596
154871
|
`);
|
|
154597
154872
|
process.exit(code);
|
|
154598
154873
|
}
|
|
154874
|
+
async function writeArtifactAtomically(out, bytes) {
|
|
154875
|
+
const temp = (0, import_node_path.join)((0, import_node_path.dirname)(out), `.${(0, import_node_path.basename)(out)}.${process.pid}.${(0, import_node_crypto.randomUUID)()}.tmp`);
|
|
154876
|
+
try {
|
|
154877
|
+
await (0, import_promises.writeFile)(temp, Buffer.from(bytes), { flag: "wx" });
|
|
154878
|
+
await (0, import_promises.rename)(temp, out);
|
|
154879
|
+
} catch (error) {
|
|
154880
|
+
try {
|
|
154881
|
+
await (0, import_promises.rm)(temp, { force: true });
|
|
154882
|
+
} catch (cleanupError) {
|
|
154883
|
+
process.stderr.write(`doc-gen: cleanup failed for ${temp}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}
|
|
154884
|
+
`);
|
|
154885
|
+
}
|
|
154886
|
+
throw error;
|
|
154887
|
+
}
|
|
154888
|
+
}
|
|
154599
154889
|
async function renderDesignHtml(spec, theme) {
|
|
154600
154890
|
const { html, unsupported } = buildDeckHtml(spec, theme);
|
|
154601
154891
|
if (unsupported.length) {
|
|
@@ -154698,6 +154988,11 @@ async function main() {
|
|
|
154698
154988
|
} catch (e) {
|
|
154699
154989
|
die(`bad spec: ${e.message}`, 64);
|
|
154700
154990
|
}
|
|
154991
|
+
try {
|
|
154992
|
+
assertDocumentSpec(kind, spec);
|
|
154993
|
+
} catch (e) {
|
|
154994
|
+
die(e instanceof Error ? e.message : String(e), e?.code === "DOC_SPEC_INVALID" ? 65 : 64);
|
|
154995
|
+
}
|
|
154701
154996
|
if (paletteFlag) spec.palette = paletteFlag;
|
|
154702
154997
|
if (topicFlag) spec.topic = topicFlag;
|
|
154703
154998
|
let rawTheme = spec.theme;
|
|
@@ -154751,8 +155046,10 @@ async function main() {
|
|
|
154751
155046
|
if (kind === "pdf") engine = "pdf (pdf-lib)";
|
|
154752
155047
|
else if (kind === "pptx" && design === "html") engine = "pptx (native fallback)";
|
|
154753
155048
|
}
|
|
154754
|
-
await (
|
|
155049
|
+
await writeArtifactAtomically(out, bytes);
|
|
154755
155050
|
process.stdout.write(`wrote ${out}${kind === "pdf" || kind === "pptx" && design === "html" ? ` [${engine}]` : ""}
|
|
154756
155051
|
`);
|
|
154757
155052
|
}
|
|
154758
|
-
main()
|
|
155053
|
+
main().catch((error) => {
|
|
155054
|
+
die(error instanceof Error ? error.message : String(error), 1);
|
|
155055
|
+
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -205,6 +205,12 @@ Simple spec shapes:
|
|
|
205
205
|
- **xlsx**: `{ "sheets": { "SheetName": [["h1","h2"], ["a", 1], ...] } }` (row 1 = bold header)
|
|
206
206
|
- **pptx**: `{ "slides": [{ "title"?: "...", "bullets"?: ["...", ...] }] }`
|
|
207
207
|
|
|
208
|
+
`doc-gen` validates these shapes **strictly before rendering**: unknown fields are
|
|
209
|
+
errors, not ignored. For DOCX/PDF use `paragraphs` and/or `blocks`—never
|
|
210
|
+
`sections`. A title-only artifact also fails unless it is intentional and the
|
|
211
|
+
spec explicitly sets `"allowCoverOnly": true`; on `DOC_SPEC_INVALID`, correct
|
|
212
|
+
the reported JSON path and rerun rather than delivering the previous output.
|
|
213
|
+
|
|
208
214
|
Rich spec shapes (use these for real documents, not just prose):
|
|
209
215
|
- **docx** `blocks`: ordered list of
|
|
210
216
|
- `{ "type": "heading", "text": "...", "level": 1..4 }`
|
package/mcp/.gitkeep
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Placeholder for shared MCP servers.
|
|
2
|
-
#
|
|
3
|
-
# Each shared MCP package lives at `mcp/<pkg>/mcp.json` — the client extracts
|
|
4
|
-
# this directory tree into `~/.talos/bundled/shared/mcp/` and every OSE
|
|
5
|
-
# auto-merges the servers in without opt-in (P12b / docs/02-ose-and-prompt.md). The mcp.json
|
|
6
|
-
# shape matches per-OSE `bundled/<id>/mcp.json`: `{ version, servers: { ... } }`.
|
|
7
|
-
#
|
|
8
|
-
# Delete this file once at least one real shared MCP package lands here.
|
package/skills/.gitkeep
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Placeholder for shared skills.
|
|
2
|
-
#
|
|
3
|
-
# Each shared skill lives at `skills/<name>/SKILL.md` — the client extracts
|
|
4
|
-
# this directory tree into `~/.talos/bundled/shared/skills/` and every OSE
|
|
5
|
-
# auto-loads the skills without opt-in (P12b / docs/02-ose-and-prompt.md).
|
|
6
|
-
#
|
|
7
|
-
# Delete this file once at least one real shared skill lands here.
|