@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.
Files changed (32) hide show
  1. package/package.json +3 -3
  2. package/skills/document/SKILL.md +1 -1
  3. package/skills/document/bin/deck-build.cjs +0 -0
  4. package/skills/document/bin/deck-qa.cjs +0 -0
  5. package/skills/document/bin/doc-convert.cjs +277 -3
  6. package/skills/document/bin/doc-fill.cjs +0 -0
  7. package/skills/document/bin/doc-gen.cjs +307 -10
  8. package/skills/document/bin/doc-run.cjs +0 -0
  9. package/skills/document/bin/doc-scan.cjs +0 -0
  10. package/skills/document/bin/doc-text.cjs +0 -0
  11. package/skills/document/bin/doc-validate.cjs +0 -0
  12. package/skills/document/bin/doc-xml.cjs +0 -0
  13. package/skills/document/bin/docx-annotate.cjs +0 -0
  14. package/skills/document/bin/html2pptx.cjs +0 -0
  15. package/skills/document/bin/ocr-worker.cjs +0 -0
  16. package/skills/document/bin/ocr.cjs +0 -0
  17. package/skills/document/bin/pdf-form.cjs +0 -0
  18. package/skills/document/bin/pdf-images.cjs +0 -0
  19. package/skills/document/bin/pdf-ops.cjs +0 -0
  20. package/skills/document/bin/pdf-render.cjs +0 -0
  21. package/skills/document/bin/pdf-tables.cjs +0 -0
  22. package/skills/document/bin/pptx-clone.cjs +0 -0
  23. package/skills/document/bin/pptx-embed-fonts.cjs +0 -0
  24. package/skills/document/bin/pptx-set-image.cjs +0 -0
  25. package/skills/document/bin/pptx-slide.cjs +0 -0
  26. package/skills/document/bin/pptx-theme.cjs +0 -0
  27. package/skills/document/bin/pptx-tokenize.cjs +0 -0
  28. package/skills/document/bin/xlsx-query.cjs +0 -0
  29. package/skills/document/bin/xlsx-recalc.cjs +0 -0
  30. package/skills/document/rewrite.md +6 -0
  31. package/mcp/.gitkeep +0 -8
  32. package/skills/.gitkeep +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taloswork/ose-shared",
3
- "version": "0.0.5",
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-lL5368bfc0Q4tq2TUv6gxgTk5TrR6nkjMCuUlj2_yRo",
14
- "_talosPublishedAt": "2026-08-26T02:54:29.336Z"
13
+ "_talosPublishHash": "sha256-qYMkSCYzW7s-GEXLCu9xYsIMRDV_FT9ZUxNJ4Dbkctk",
14
+ "_talosPublishedAt": "2026-08-28T05:55:50.205Z"
15
15
  }
@@ -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
- if (!spec || typeof spec !== "object")
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 rename(from, to, cb) {
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(rename, fs$rename);
36501
- return rename;
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 basename = path.basename(name);
72025
- if (basename === ".." || basename === "." || basename !== name) {
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
- if (!spec || typeof spec !== "object")
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 (0, import_promises.writeFile)(out, Buffer.from(bytes));
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.