@taloswork/ose-shared 0.1.11 → 0.1.13

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 (159) hide show
  1. package/package.json +3 -3
  2. package/skills/document/SKILL.md +135 -219
  3. package/skills/document/art-directions/base.css +27 -2
  4. package/skills/document/art-directions/cinematic-depth.css +3 -1
  5. package/skills/document/art-directions/collage-paper.css +5 -1
  6. package/skills/document/art-directions/editorial-cut.css +5 -1
  7. package/skills/document/art-directions/geometric-pop.css +6 -0
  8. package/skills/document/art-directions/gradient-mesh.css +5 -1
  9. package/skills/document/art-directions/illustrated-doodle.css +5 -0
  10. package/skills/document/art-directions/luxury-ornament.css +4 -1
  11. package/skills/document/art-directions/maximalist-blocks.css +6 -0
  12. package/skills/document/art-directions/technical-grid.css +4 -0
  13. package/skills/document/assets/fonts/fonts.json +2 -2
  14. package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Bold.ttf +0 -0
  15. package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Regular.ttf +0 -0
  16. package/skills/document/assets/pdf-fonts/OFL.txt +93 -0
  17. package/skills/document/assets/pdf-fonts/README.md +35 -0
  18. package/skills/document/assets/tessdata/LICENSE-Apache-2.0.txt +202 -0
  19. package/skills/document/assets/tessdata/README.md +21 -0
  20. package/skills/document/assets/tessdata/chi_sim.traineddata.gz +0 -0
  21. package/skills/document/assets/tessdata/eng.traineddata.gz +0 -0
  22. package/skills/document/bin/deck-build.cjs +12563 -83
  23. package/skills/document/bin/deck-chart.cjs +771 -0
  24. package/skills/document/bin/deck-plan.cjs +12414 -0
  25. package/skills/document/bin/deck-qa.cjs +1075 -266
  26. package/skills/document/bin/deck-scenario.cjs +1363 -0
  27. package/skills/document/bin/deck-styles.cjs +1604 -0
  28. package/skills/document/bin/doc-convert.cjs +308 -292
  29. package/skills/document/bin/doc-fill.cjs +73264 -192
  30. package/skills/document/bin/doc-gen.cjs +2491 -312
  31. package/skills/document/bin/doc-run.cjs +162 -24
  32. package/skills/document/bin/doc-scan.cjs +13 -0
  33. package/skills/document/bin/doc-text.cjs +13 -0
  34. package/skills/document/bin/doc-validate.cjs +838 -68
  35. package/skills/document/bin/doc-xml.cjs +9 -5
  36. package/skills/document/bin/docx-annotate.cjs +95 -19
  37. package/skills/document/bin/docx-defaults.cjs +9907 -0
  38. package/skills/document/bin/fontkit.cjs +1 -0
  39. package/skills/document/bin/html2pptx.cjs +36622 -31394
  40. package/skills/document/bin/ocr-worker.cjs +1 -0
  41. package/skills/document/bin/ocr.cjs +131 -40
  42. package/skills/document/bin/pdf-form.cjs +682 -177
  43. package/skills/document/bin/pdf-images.cjs +13 -0
  44. package/skills/document/bin/pdf-ops.cjs +642 -199
  45. package/skills/document/bin/pdf-render.cjs +19 -3
  46. package/skills/document/bin/pdf-tables.cjs +13 -0
  47. package/skills/document/bin/pptx-clone.cjs +3 -3
  48. package/skills/document/bin/pptx-notes.cjs +10038 -0
  49. package/skills/document/bin/pptx-slide.cjs +3 -3
  50. package/skills/document/bin/pptx-tokenize.cjs +19 -4
  51. package/skills/document/bin/template-skin.cjs +10910 -0
  52. package/skills/document/bin/xlsx-query.cjs +192 -61
  53. package/skills/document/bin-reference.md +61 -33
  54. package/skills/document/catalog.json +33 -32
  55. package/skills/document/content-pipeline.md +179 -257
  56. package/skills/document/deck-design.md +378 -638
  57. package/skills/document/generated-visuals.md +99 -0
  58. package/skills/document/layouts/MAINTAINERS.md +33 -0
  59. package/skills/document/layouts/README.md +49 -36
  60. package/skills/document/layouts/big-stat.tpl +1 -1
  61. package/skills/document/layouts/capacity.json +1 -0
  62. package/skills/document/layouts/cards-3.tpl +1 -1
  63. package/skills/document/layouts/chart-full.tpl +1 -1
  64. package/skills/document/layouts/chart-kpi.tpl +1 -1
  65. package/skills/document/layouts/comparison.tpl +1 -1
  66. package/skills/document/layouts/data-table.tpl +1 -1
  67. package/skills/document/layouts/funnel.tpl +1 -1
  68. package/skills/document/layouts/gantt.tpl +1 -1
  69. package/skills/document/layouts/hub-flow.tpl +4 -0
  70. package/skills/document/layouts/icon-row.tpl +1 -1
  71. package/skills/document/layouts/image-grid.tpl +1 -1
  72. package/skills/document/layouts/matrix-2x2.tpl +1 -1
  73. package/skills/document/layouts/mock-qa.tpl +4 -0
  74. package/skills/document/layouts/profiles.json +2 -2
  75. package/skills/document/layouts/pyramid.tpl +1 -1
  76. package/skills/document/layouts/radar.tpl +1 -1
  77. package/skills/document/layouts/ring-progress.tpl +1 -1
  78. package/skills/document/layouts/slots.json +1 -1
  79. package/skills/document/layouts/steps-vertical.tpl +1 -1
  80. package/skills/document/layouts/steps.tpl +1 -1
  81. package/skills/document/layouts/three-kpi.tpl +1 -1
  82. package/skills/document/layouts/timeline.tpl +1 -1
  83. package/skills/document/layouts/toc-panel.tpl +2 -2
  84. package/skills/document/layouts/two-kpi.tpl +2 -2
  85. package/skills/document/layouts/versus.tpl +1 -1
  86. package/skills/document/motion/README.md +3 -2
  87. package/skills/document/office.md +3 -3
  88. package/skills/document/packs/README.md +1 -1
  89. package/skills/document/packs/board-brief/backdrop.html +2 -0
  90. package/skills/document/packs/board-brief/pack.css +2 -1
  91. package/skills/document/packs/mckinsey-photo/backdrop.html +2 -0
  92. package/skills/document/packs/mckinsey-photo/pack.css +2 -1
  93. package/skills/document/packs/press-room/backdrop.html +2 -0
  94. package/skills/document/packs/press-room/pack.css +2 -1
  95. package/skills/document/packs/prismatic-3d/backdrop.html +2 -0
  96. package/skills/document/packs/prismatic-3d/pack.css +4 -1
  97. package/skills/document/pdf.md +60 -43
  98. package/skills/document/rewrite.md +257 -309
  99. package/skills/document/scenarios.json +1307 -0
  100. package/skills/document/scenarios.schema.json +104 -0
  101. package/skills/document/styles/README.md +15 -25
  102. package/skills/document/styles/_backdrops/rimlight.html +3 -2
  103. package/skills/document/styles/_contract-defaults.css +31 -2
  104. package/skills/document/styles/_samples/_groups/biz-premium.webp +0 -0
  105. package/skills/document/styles/_samples/mckinsey.webp +0 -0
  106. package/skills/document/styles/anthropic/skin.css +6 -3
  107. package/skills/document/styles/apple/skin.css +4 -3
  108. package/skills/document/styles/bistro-warm/backdrop.html +2 -0
  109. package/skills/document/styles/bistro-warm/skin.css +4 -1
  110. package/skills/document/styles/black-gold/skin.css +6 -5
  111. package/skills/document/styles/bold-clash/backdrop.html +2 -0
  112. package/skills/document/styles/bold-clash/skin.css +2 -1
  113. package/skills/document/styles/branding/skin.css +4 -4
  114. package/skills/document/styles/campus-teal/backdrop.html +2 -0
  115. package/skills/document/styles/campus-teal/skin.css +2 -1
  116. package/skills/document/styles/cinereel/skin.css +7 -3
  117. package/skills/document/styles/clinic-care/backdrop.html +2 -0
  118. package/skills/document/styles/clinic-care/skin.css +2 -1
  119. package/skills/document/styles/cool/skin.css +6 -5
  120. package/skills/document/styles/dark-glass/backdrop.html +2 -0
  121. package/skills/document/styles/dark-glass/skin.css +2 -1
  122. package/skills/document/styles/gov-brief/backdrop.html +2 -0
  123. package/skills/document/styles/gov-brief/skin.css +2 -1
  124. package/skills/document/styles/grid-tech/backdrop.html +2 -0
  125. package/skills/document/styles/grid-tech/skin.css +2 -1
  126. package/skills/document/styles/hand-drawn/skin.css +3 -3
  127. package/skills/document/styles/ivory-gold/skin.css +7 -6
  128. package/skills/document/styles/legal-slate/backdrop.html +2 -0
  129. package/skills/document/styles/legal-slate/skin.css +2 -1
  130. package/skills/document/styles/magazine-ink/skin.css +4 -3
  131. package/skills/document/styles/mckinsey/backdrop.html +7 -5
  132. package/skills/document/styles/mckinsey/skin.css +2 -1
  133. package/skills/document/styles/memphis/backdrop.html +2 -0
  134. package/skills/document/styles/memphis/skin.css +2 -1
  135. package/skills/document/styles/mono/skin.css +6 -2
  136. package/skills/document/styles/neon-cyber/backdrop.html +2 -0
  137. package/skills/document/styles/neon-cyber/skin.css +2 -1
  138. package/skills/document/styles/olive-gold/skin.css +7 -6
  139. package/skills/document/styles/organic-earth/backdrop.html +2 -0
  140. package/skills/document/styles/organic-earth/skin.css +2 -1
  141. package/skills/document/styles/pastel-saas/backdrop.html +2 -0
  142. package/skills/document/styles/pastel-saas/skin.css +2 -1
  143. package/skills/document/styles/portfolio-noir/backdrop.html +2 -0
  144. package/skills/document/styles/portfolio-noir/skin.css +2 -1
  145. package/skills/document/styles/retro-press/backdrop.html +2 -0
  146. package/skills/document/styles/retro-press/skin.css +2 -1
  147. package/skills/document/styles/sport-arena/backdrop.html +2 -0
  148. package/skills/document/styles/sport-arena/skin.css +4 -1
  149. package/skills/document/styles/swiss-grid/skin.css +10 -5
  150. package/skills/document/styles/wedding-blush/backdrop.html +2 -0
  151. package/skills/document/styles/wedding-blush/skin.css +4 -1
  152. package/skills/document/theme-system.md +85 -77
  153. package/skills/document/themes/README.md +7 -5
  154. package/skills/document/vision.md +8 -5
  155. package/skills/document/xlsx.md +15 -25
  156. package/skills/grill-me/SKILL.md +6 -0
  157. package/skills/grill-me/agents/openai.yaml +5 -0
  158. package/skills/grilling/SKILL.md +28 -0
  159. package/skills/grilling/agents/openai.yaml +3 -0
@@ -12683,7 +12683,7 @@ var require_xlsx = __commonJS({
12683
12683
  var s = o.dense ? [] : {}, n = "Sheet1", next_n = "", sidx = 0;
12684
12684
  var sheets = {}, snames = [], realnames = [];
12685
12685
  var refguess = { s: { r: 0, c: 0 }, e: { r: 0, c: 0 } };
12686
- var sheetRows2 = o.sheetRows || 0;
12686
+ var sheetRows = o.sheetRows || 0;
12687
12687
  if (d[2] == 0) {
12688
12688
  if (d[3] == 8 || d[3] == 9) {
12689
12689
  if (d.length >= 16 && d[14] == 5 && d[15] === 108) throw new Error("Unsupported Works 3 for Mac file");
@@ -12789,7 +12789,7 @@ var require_xlsx = __commonJS({
12789
12789
  sidx = val[3];
12790
12790
  n = "Sheet" + (sidx + 1);
12791
12791
  }
12792
- if (sheetRows2 > 0 && val[0].r >= sheetRows2) break;
12792
+ if (sheetRows > 0 && val[0].r >= sheetRows) break;
12793
12793
  if (o.dense) {
12794
12794
  if (!s[val[0].r]) s[val[0].r] = [];
12795
12795
  s[val[0].r][val[0].c] = val[1];
@@ -29309,7 +29309,7 @@ var require_xlsx = __commonJS({
29309
29309
  }
29310
29310
  }
29311
29311
  var rows = table.getElementsByTagName("tr");
29312
- var sheetRows2 = Math.min(opts.sheetRows || 1e7, rows.length);
29312
+ var sheetRows = Math.min(opts.sheetRows || 1e7, rows.length);
29313
29313
  var range = { s: { r: 0, c: 0 }, e: { r: or_R, c: or_C } };
29314
29314
  if (ws["!ref"]) {
29315
29315
  var _range = decode_range(ws["!ref"]);
@@ -29323,7 +29323,7 @@ var require_xlsx = __commonJS({
29323
29323
  var rowinfo = ws["!rows"] || (ws["!rows"] = []);
29324
29324
  var _R = 0, R = 0, _C = 0, C = 0, RS = 0, CS = 0;
29325
29325
  if (!ws["!cols"]) ws["!cols"] = [];
29326
- for (; _R < rows.length && R < sheetRows2; ++_R) {
29326
+ for (; _R < rows.length && R < sheetRows; ++_R) {
29327
29327
  var row = rows[_R];
29328
29328
  if (is_dom_element_hidden(row)) {
29329
29329
  if (opts.display) continue;
@@ -29379,7 +29379,7 @@ var require_xlsx = __commonJS({
29379
29379
  if (merges.length) ws["!merges"] = (ws["!merges"] || []).concat(merges);
29380
29380
  range.e.r = Math.max(range.e.r, R - 1 + or_R);
29381
29381
  ws["!ref"] = encode_range(range);
29382
- if (R >= sheetRows2) ws["!fullref"] = encode_range((range.e.r = rows.length - _R + R - 1 + or_R, range));
29382
+ if (R >= sheetRows) ws["!fullref"] = encode_range((range.e.r = rows.length - _R + R - 1 + or_R, range));
29383
29383
  return ws;
29384
29384
  }
29385
29385
  function parse_dom_table(table, _opts) {
@@ -33253,30 +33253,104 @@ var import_promises = require("node:fs/promises");
33253
33253
 
33254
33254
  // packages/core/dist/document/xlsx-query.js
33255
33255
  var XLSX = __toESM(require_xlsx(), 1);
33256
+ var AGGREGATE_OPS = ["sum", "avg", "min", "max", "count", "count-nonempty"];
33256
33257
  function parseWorkbook(buffer) {
33257
33258
  const data = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
33258
33259
  return XLSX.read(data, { type: "array" });
33259
33260
  }
33260
- function sheetRows(wb, sheetName) {
33261
+ var hasValue = (c) => c != null && c.t !== "z" && c.v != null;
33262
+ function sheetGrid(wb, sheetName) {
33261
33263
  const ws = wb.Sheets[sheetName];
33262
- if (!ws)
33263
- return [];
33264
- return XLSX.utils.sheet_to_json(ws, { header: 1, blankrows: false, defval: "" });
33264
+ if (!ws || !ws["!ref"])
33265
+ return { rows: [], width: 0, firstCol: 0 };
33266
+ const range = XLSX.utils.decode_range(ws["!ref"]);
33267
+ const rows = [];
33268
+ for (let r = range.s.r; r <= range.e.r; r++) {
33269
+ const cells = [];
33270
+ for (let c = range.s.c; c <= range.e.c; c++)
33271
+ cells.push(ws[XLSX.utils.encode_cell({ r, c })]);
33272
+ if (cells.some(hasValue))
33273
+ rows.push({ row: r + 1, cells });
33274
+ }
33275
+ return { rows, width: rows.length ? range.e.c - range.s.c + 1 : 0, firstCol: range.s.c };
33276
+ }
33277
+ function parseNumberText(text) {
33278
+ let t = text.replace(/[¥¥$€£,\s]/g, "").replace(/元$/, "");
33279
+ let sign = 1;
33280
+ const paren = /^\((.*)\)$/.exec(t);
33281
+ if (paren) {
33282
+ sign = -1;
33283
+ t = paren[1];
33284
+ }
33285
+ let scale = 1;
33286
+ if (t.endsWith("%")) {
33287
+ scale = 0.01;
33288
+ t = t.slice(0, -1);
33289
+ }
33290
+ if (t === "" || !/^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i.test(t))
33291
+ return null;
33292
+ const n = Number(t);
33293
+ return Number.isFinite(n) ? cleanFloat(sign * n * scale) : null;
33294
+ }
33295
+ function cellNumber(c) {
33296
+ if (!hasValue(c))
33297
+ return null;
33298
+ if (c.t === "n")
33299
+ return typeof c.v === "number" && Number.isFinite(c.v) ? c.v : null;
33300
+ if (c.t === "s")
33301
+ return parseNumberText(String(c.v));
33302
+ return null;
33303
+ }
33304
+ var isEmptyCell = (c) => !hasValue(c) || String(c.v).trim() === "";
33305
+ var isTextCell = (c) => !isEmptyCell(c) && c.t === "s" && cellNumber(c) == null;
33306
+ function cleanFloat(v) {
33307
+ if (!Number.isFinite(v) || v === 0)
33308
+ return v;
33309
+ return Number(v.toPrecision(15));
33310
+ }
33311
+ var HEADER_SCAN_ROWS = 10;
33312
+ function detectHeaderIndex(grid) {
33313
+ const { rows, width } = grid;
33314
+ for (let k = 0; k < Math.min(HEADER_SCAN_ROWS, rows.length - 1); k++) {
33315
+ const cells = rows[k].cells;
33316
+ const filled = cells.filter((c) => !isEmptyCell(c));
33317
+ if (!filled.length || filled.length * 2 <= width)
33318
+ continue;
33319
+ if (cells.filter(isTextCell).length * 2 < filled.length)
33320
+ continue;
33321
+ if (rows[k + 1].cells.some((c) => cellNumber(c) != null))
33322
+ return k;
33323
+ }
33324
+ return 0;
33265
33325
  }
33266
- function headerRow(rows) {
33267
- const first = rows[0] ?? [];
33268
- return first.map((c, i) => {
33269
- const s = c == null ? "" : String(c).trim();
33326
+ function layout(grid, opts) {
33327
+ if (!grid.rows.length)
33328
+ return { headerRow: opts.headerRow ?? 1, headers: [], data: [] };
33329
+ let headerCells;
33330
+ let headerRow;
33331
+ let data;
33332
+ if (opts.headerRow != null) {
33333
+ headerRow = opts.headerRow;
33334
+ headerCells = grid.rows.find((r) => r.row === headerRow)?.cells ?? [];
33335
+ data = grid.rows.filter((r) => r.row > headerRow);
33336
+ } else {
33337
+ const k = detectHeaderIndex(grid);
33338
+ headerRow = grid.rows[k].row;
33339
+ headerCells = grid.rows[k].cells;
33340
+ data = grid.rows.slice(k + 1);
33341
+ }
33342
+ const headers = Array.from({ length: grid.width }, (_, i) => {
33343
+ const c = headerCells[i];
33344
+ const s = hasValue(c) ? String(c.v).trim() : "";
33270
33345
  return s.length > 0 ? s : `col${i + 1}`;
33271
33346
  });
33347
+ return { headerRow, headers, data };
33272
33348
  }
33273
- function summarizeWorkbook(wb) {
33349
+ function summarizeWorkbook(wb, opts = {}) {
33274
33350
  const sheets = wb.SheetNames.map((name) => {
33275
- const rows = sheetRows(wb, name);
33276
- const headers = rows.length > 0 ? headerRow(rows) : [];
33277
- const dataRows = rows.length > 0 ? rows.length - 1 : 0;
33278
- const cols = rows.reduce((m, r) => Math.max(m, r.length), 0);
33279
- return { name, rows: dataRows, cols, headers };
33351
+ const grid = sheetGrid(wb, name);
33352
+ const { headerRow, headers, data } = layout(grid, opts);
33353
+ return { name, rows: data.length, cols: grid.width, headerRow, headers };
33280
33354
  });
33281
33355
  return { sheets };
33282
33356
  }
@@ -33289,54 +33363,95 @@ function resolveColumnIndex(headers, column) {
33289
33363
  return asNum - 1;
33290
33364
  return -1;
33291
33365
  }
33292
- function toNumber(cell) {
33293
- if (typeof cell === "number" && Number.isFinite(cell))
33294
- return cell;
33295
- if (typeof cell === "string") {
33296
- const cleaned = cell.replace(/[$,\s%]/g, "");
33297
- if (cleaned === "")
33298
- return null;
33299
- const n = Number(cleaned);
33300
- return Number.isFinite(n) ? n : null;
33366
+ var TOTAL_LABEL = /^(合计|总计|小计|total|subtotal|sum)$/i;
33367
+ function totalReason(row, colIdx, colLetter) {
33368
+ for (const c of row.cells) {
33369
+ if (!hasValue(c) || c.t !== "s")
33370
+ continue;
33371
+ const label = String(c.v).replace(/\s+/g, "").replace(/[::]$/, "");
33372
+ if (TOTAL_LABEL.test(label))
33373
+ return `total label "${String(c.v).trim()}"`;
33374
+ }
33375
+ const f = row.cells[colIdx]?.f;
33376
+ if (f) {
33377
+ const m = /^\s*=?\s*SUM\(\s*\$?([A-Z]+)\$?\d+\s*:\s*\$?([A-Z]+)\$?\d+\s*\)\s*$/i.exec(f);
33378
+ if (m && m[1].toUpperCase() === colLetter && m[2].toUpperCase() === colLetter)
33379
+ return `${f.trim().replace(/^=/, "")} over this column`;
33301
33380
  }
33302
33381
  return null;
33303
33382
  }
33304
- function aggregateColumn(wb, sheetName, column, op) {
33305
- const rows = sheetRows(wb, sheetName);
33306
- const colIdx = rows.length > 0 ? resolveColumnIndex(headerRow(rows), column) : -1;
33307
- const base = { sheet: sheetName, column, op, colIdx };
33308
- if (rows.length === 0)
33309
- return { ...base, rowsScanned: 0, numericCells: 0, value: null };
33310
- const dataRows = rows.slice(1);
33311
- if (colIdx === -1)
33312
- return { ...base, rowsScanned: dataRows.length, numericCells: 0, value: null };
33313
- const cells = dataRows.map((r) => r[colIdx]);
33383
+ var SKIPPED_CELLS_CAP = 20;
33384
+ function aggregateColumn(wb, sheetName, column, op, opts = {}) {
33385
+ const grid = sheetGrid(wb, sheetName);
33386
+ const { headerRow, headers, data } = layout(grid, opts);
33387
+ const colIdx = headers.length > 0 ? resolveColumnIndex(headers, column) : -1;
33388
+ const result = {
33389
+ sheet: sheetName,
33390
+ column,
33391
+ op,
33392
+ headerRow,
33393
+ colIdx,
33394
+ rowsScanned: 0,
33395
+ numericCells: 0,
33396
+ value: null,
33397
+ excludedRows: [],
33398
+ skippedCells: [],
33399
+ skippedCellCount: 0
33400
+ };
33401
+ if (colIdx === -1) {
33402
+ result.rowsScanned = data.length;
33403
+ return result;
33404
+ }
33405
+ const colLetter = XLSX.utils.encode_col(grid.firstCol + colIdx);
33406
+ const kept = [];
33407
+ for (const r of data) {
33408
+ const reason = opts.includeTotals ? null : totalReason(r, colIdx, colLetter);
33409
+ if (reason)
33410
+ result.excludedRows.push({ row: r.row, reason });
33411
+ else
33412
+ kept.push(r);
33413
+ }
33414
+ result.rowsScanned = kept.length;
33314
33415
  if (op === "count") {
33315
- return { ...base, rowsScanned: dataRows.length, numericCells: 0, value: dataRows.length };
33416
+ result.value = kept.length;
33417
+ return result;
33316
33418
  }
33419
+ const cells = kept.map((r) => ({ row: r.row, cell: r.cells[colIdx] }));
33317
33420
  if (op === "count-nonempty") {
33318
- const nonEmpty = cells.filter((c) => c != null && String(c).trim() !== "").length;
33319
- return { ...base, rowsScanned: dataRows.length, numericCells: 0, value: nonEmpty };
33421
+ result.value = cells.filter(({ cell }) => !isEmptyCell(cell)).length;
33422
+ return result;
33320
33423
  }
33321
- const nums = cells.map(toNumber).filter((n) => n != null);
33424
+ const nums = [];
33425
+ for (const { row, cell } of cells) {
33426
+ if (isEmptyCell(cell))
33427
+ continue;
33428
+ const n = cellNumber(cell);
33429
+ if (n != null) {
33430
+ nums.push(n);
33431
+ continue;
33432
+ }
33433
+ result.skippedCellCount++;
33434
+ if (result.skippedCells.length < SKIPPED_CELLS_CAP)
33435
+ result.skippedCells.push({ row, value: cell.v });
33436
+ }
33437
+ result.numericCells = nums.length;
33322
33438
  if (nums.length === 0)
33323
- return { ...base, rowsScanned: dataRows.length, numericCells: 0, value: null };
33324
- let value;
33439
+ return result;
33325
33440
  switch (op) {
33326
33441
  case "sum":
33327
- value = nums.reduce((a, b) => a + b, 0);
33442
+ result.value = cleanFloat(nums.reduce((a, b) => a + b, 0));
33328
33443
  break;
33329
33444
  case "avg":
33330
- value = nums.reduce((a, b) => a + b, 0) / nums.length;
33445
+ result.value = cleanFloat(nums.reduce((a, b) => a + b, 0) / nums.length);
33331
33446
  break;
33332
33447
  case "min":
33333
- value = Math.min(...nums);
33448
+ result.value = Math.min(...nums);
33334
33449
  break;
33335
33450
  case "max":
33336
- value = Math.max(...nums);
33451
+ result.value = Math.max(...nums);
33337
33452
  break;
33338
33453
  }
33339
- return { ...base, rowsScanned: dataRows.length, numericCells: nums.length, value };
33454
+ return result;
33340
33455
  }
33341
33456
  var COMPUTE_KEYWORDS_EN = [
33342
33457
  "sum",
@@ -33356,15 +33471,13 @@ var COMPUTE_KEYWORDS_EN = [
33356
33471
  "percentage"
33357
33472
  ];
33358
33473
  var COMPUTE_EN_RE = new RegExp(`\\b(${COMPUTE_KEYWORDS_EN.map((k) => k.replace(/ /g, "\\s+")).join("|")})\\b`, "i");
33359
- function sampleRows(wb, sheetName, limit = 20) {
33360
- const rows = sheetRows(wb, sheetName);
33361
- if (rows.length <= 1)
33362
- return [];
33363
- const headers = headerRow(rows);
33364
- return rows.slice(1, 1 + Math.max(0, limit)).map((r) => {
33474
+ function sampleRows(wb, sheetName, limit = 20, opts = {}) {
33475
+ const { headers, data } = layout(sheetGrid(wb, sheetName), opts);
33476
+ return data.slice(0, Math.max(0, limit)).map((r) => {
33365
33477
  const obj = {};
33366
33478
  headers.forEach((h, i) => {
33367
- obj[h] = r[i] ?? "";
33479
+ const c = r.cells[i];
33480
+ obj[h] = hasValue(c) ? c.v : "";
33368
33481
  });
33369
33482
  return obj;
33370
33483
  });
@@ -33379,9 +33492,24 @@ function die(msg, code = 1) {
33379
33492
  function out(obj) {
33380
33493
  process.stdout.write(JSON.stringify(obj, null, 2) + "\n");
33381
33494
  }
33495
+ var USAGE = "usage: xlsx-query <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]] [--header-row N] [--include-totals]";
33496
+ function parseFlags(argv) {
33497
+ const opts = {};
33498
+ const positional = [];
33499
+ for (let i = 0; i < argv.length; i++) {
33500
+ if (argv[i] === "--include-totals") opts.includeTotals = true;
33501
+ else if (argv[i] === "--header-row") {
33502
+ const n = Number(argv[++i]);
33503
+ if (!Number.isInteger(n) || n < 1) die("--header-row needs a 1-based row number", 64);
33504
+ opts.headerRow = n;
33505
+ } else positional.push(argv[i]);
33506
+ }
33507
+ return { opts, positional };
33508
+ }
33382
33509
  async function main() {
33383
- const [file, mode, ...rest] = process.argv.slice(2);
33384
- if (!file) die("usage: xlsx-query <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]]", 64);
33510
+ const { opts, positional } = parseFlags(process.argv.slice(2));
33511
+ const [file, mode, ...rest] = positional;
33512
+ if (!file) die(USAGE, 64);
33385
33513
  let raw;
33386
33514
  try {
33387
33515
  raw = await (0, import_promises.readFile)(file);
@@ -33395,19 +33523,22 @@ async function main() {
33395
33523
  die(`parse failed: ${e.message}`);
33396
33524
  }
33397
33525
  if (!mode || mode === "--summary") {
33398
- out(summarizeWorkbook(wb));
33526
+ out(summarizeWorkbook(wb, opts));
33399
33527
  return;
33400
33528
  }
33401
33529
  if (mode === "--agg") {
33402
33530
  const [sheet, col, op] = rest;
33403
33531
  if (!sheet || !col || !op) die("usage: --agg <sheet> <col> <op>", 64);
33404
- out(aggregateColumn(wb, sheet, col, op));
33532
+ if (!AGGREGATE_OPS.includes(op)) die(`unknown op "${op}" \u2014 use one of: ${AGGREGATE_OPS.join(", ")}`, 64);
33533
+ if (!wb.SheetNames.includes(sheet)) die(`no sheet "${sheet}" \u2014 the workbook has: ${wb.SheetNames.join(", ")}`);
33534
+ out(aggregateColumn(wb, sheet, col, op, opts));
33405
33535
  return;
33406
33536
  }
33407
33537
  if (mode === "--sample") {
33408
33538
  const [sheet, n] = rest;
33409
33539
  if (!sheet) die("usage: --sample <sheet> [n]", 64);
33410
- out({ sheet, sample: sampleRows(wb, sheet, Math.max(0, Number(n) || 20)) });
33540
+ if (!wb.SheetNames.includes(sheet)) die(`no sheet "${sheet}" \u2014 the workbook has: ${wb.SheetNames.join(", ")}`);
33541
+ out({ sheet, sample: sampleRows(wb, sheet, Math.max(0, Number(n) || 20), opts) });
33411
33542
  return;
33412
33543
  }
33413
33544
  die(`unknown mode: ${mode}`, 64);
@@ -1,35 +1,63 @@
1
1
  # Script reference (L3)
2
2
 
3
- Every bin in `bin/`, with its full contract. SKILL.md lists them one line each;
4
- read the entry here before the first call of a bin you have not used in this task.
5
-
6
- - `bin/doc-scan.cjs <file.pdf>` — per-page signals as JSON.
7
- - `bin/doc-text.cjs <file.pdf>` — extract a PDF's text layer.
8
- - `bin/pdf-render.cjs <file.pdf> <pages|all> [scale]` — render pages to PNG for vision (`pages` = `3` | `2-5` | `3,7-8` | `all`). `scale` (default `2` ≈ 144dpi) trades resolution for vision tokens: pass `1` (≈72dpi, ¼ the pixels ≈ ¼ the tokens) for slide-composition/layout QA; keep `2` when fine print must stay legible (formulas/tables). Reading previews is the priciest step in the deck loop — render cheap + read only what changed (a deck's final Office pass hands you its images: `deck-qa --final`).
9
- - `bin/pdf-images.cjs <file.pdf> [pages]` — extract EMBEDDED images at native resolution with page placement (`pages` = `N` | `N-M` | `all`, default all). Prints a JSON manifest (page/index/bbox/size/path). Use this — not a full-page `pdf-render` — when you need the original figures/photos as their own files (e.g. rebuilding a deck). Vector/text-only pages yield nothing → fall back to `pdf-render`.
10
- - `bin/pdf-tables.cjs <file.pdf> [page] [minCols]` — recover text-layer tables as rows (ruled/scanned tables need render + vision instead).
11
- - `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.
12
- - `bin/xlsx-query.cjs <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]]` — spreadsheet compute.
13
- - `bin/doc-gen.cjs <docx|xlsx|pdf> <out> <spec.json> [--theme <theme.json>] [--topic <subject> | --palette <id>] [--no-preview]` — generate documents from scratch. DOCX/PDF body fields are exactly `paragraphs` and/or `blocks` (never `sections`); in a PDF, any http(s) URL written in body text, list items or table cells becomes a clickable link and wraps inside the page on its own — never split a URL by hand (there is no other link syntax); 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 — see rewrite.md "Themes"; built-ins in `themes/`). **`--topic <subject>` (when no `--theme`) picks a topic-adaptive palette** instead of the same default every time. `doc-gen pptx` exits 64: decks are built with html2pptx. `--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.
14
- - `bin/doc-convert.cjs <in.docx|pptx|xlsx> <out.pdf> [--cache-dir <dir>]` — 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** — reconstructed HTML, content/structure/images preserved but in-document layout is not (pptx = one page per slide): the app's service in-app, else a headless Chromium; with none of these it exits 69 — say the PDF could not be made. `--cache-dir` (or `TALOS_OFFICE_CACHE_DIR`) reuses a content-hashed local LibreOffice result for the exact same immutable artifact; caching is opt-in so document bytes are not retained by default. Surface the resulting `[pdf (engine)]` caveat to the user. NOTE: spreadsheets (xlsx/xls/ods) render ONLY via (1)–(2), a real LibreOffice; Chromium does not rebuild spreadsheets, so with no LibreOffice available the command errors (never a degraded text-only sheet).
15
- - `bin/doc-validate.cjs <file.docx|pptx|xlsx|pdf> [--strict] [--design-lint]` — structural validation gate: OOXML zip/parts/rels/Content_Types/XML well-formedness; a PDF must parse and hold at least one page (reports `pages` and a `pdf-parse-only` warning — how it looks is render-and-look's job; `--strict` / `--design-lint` are OOXML-only; `--strict` on a PDF adds a `strict-not-applicable` warning). 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".
16
- - `bin/deck-qa.cjs <file.pptx> [--json] [--rendered <office.pdf> | --final [--cache-dir <dir>]]` — 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"). `html2pptx` already prints this lint as `qa` after every build. **`--final` is the deck's delivery sign-off in one call**: strict XSD validation (failure exits 1), the lint, an Office render through `doc-convert` (cached), `render-overflow` / `render-collision` where Office re-laid the native text past its box, and `review` + `previews` — the only slides (findings + native charts) to look at again, already rendered at ≈72dpi (deck-design.md "Verify before delivering"). `--rendered <pdf>` is the same render check against a PDF you converted yourself; a page count that differs from the deck exits 1.
17
- - `bin/doc-xml.cjs unpack <file> <dir>` / `pack <dir> <out>` — raw-XML escape hatch (pretty-printed unpack → edit → repack); always doc-validate afterwards. `replace <in.docx> <out.docx> <find> <replacement>` — cross-run character-offset replace (finds text even when Word split it over runs, preserves formatting; covers body + headers/footers) — try this FIRST for plain text edits before unpacking. `merge-runs <in.docx> <out.docx>` repairs run fragmentation when you do need manual XML edits.
18
- - `bin/pptx-clone.cjs <template.pptx> <slideNo> <out.pptx> [replacements.json]` — clone a styled template slide once per data item, replacing `{{tokens}}` in the clone only (tokens split across runs are still found); `pptx-clone remove <in> <slideNo> <out>` drops a slide (e.g. the leftover token template). CAPACITY warnings on stderr mean the text likely overflows its slot — shorten the wording, never shrink the font. See rewrite.md.
19
- - `bin/pptx-slide.cjs add <in.pptx> <out.pptx> [--layout N]` / `dup <in> <slideNo> <out>` / `remove <in> <slideNo> <out>` / `reorder <in> <order> <out>` — slide-lifecycle surgery on an EXISTING deck WITHOUT a `{{token}}` template: append a blank slide bound to a layout (inherits the deck's master/theme), duplicate or drop a slide, or permute deck order (`pptx-slide reorder in.pptx 3,1,2 out.pptx` — comma-list of current 1-based positions in the new sequence). Lossless & structural — parts/rels/Content_Types are bookkept for you, but in-slide TEXT/content edits still go through doc-xml. Always doc-validate.
20
- - `bin/pptx-embed-fonts.cjs <in.pptx> <out.pptx> --font "<Typeface>=<path.ttf>" [--bold …] [--italic …]` — EMBED fonts into a deck (OOXML font embedding) so it looks identical on a machine lacking the font. Full-font embed → use for SMALL custom/brand fonts; NOT big CJK fonts (openers have system CJK, and the file would balloon). `<Typeface>` must match the deck's text-run font name. doc-validate after.
21
- - `bin/pptx-theme.cjs <ref.pptx> [out.json]` — extract a theme.json (palette/fonts) from an existing deck, then `html2pptx --theme` (or `doc-gen --theme` for a matching docx/pdf) it: new content in that deck's look. Palette only — reusing actual layouts is pptx-clone/doc-fill territory.
22
- - `bin/doc-fill.cjs <template.docx|pptx|xlsx> <values.json> <out>` — fill `{{placeholders}}` in an existing template, preserving its styling (see rewrite.md).
23
- - `bin/pptx-tokenize.cjs <user.pptx> <template.pptx> [schema.json]` — turn a user's UPLOADED deck into a reusable `{{token}}` template: keeps its exact layouts/master/brand, replaces each content slot's text with a clean single-run token, and emits a `schema.json` cataloging every token (slide/role/type/multiline/original/capacity). Plain text boxes (no `<p:ph>`) are tokenized too. Then `doc-fill` it — fill EVERY token (pass `original` for slots you keep, else they blank). The B2 "reuse their layout" path; see rewrite.md.
24
- - `bin/pptx-set-image.cjs <in.pptx> <slideNo> <rId> <image> <out.pptx>` — swap the picture behind a slide's `rId` (from pptx-tokenize's image tokens) with a new image, keeping the frame (position/size) put. add-and-repoint (old media orphaned). Aspect-mismatch STRETCHES — pre-crop to the slot ratio. Always doc-validate.
25
- - `bin/docx-annotate.cjs comment <in.docx> <out.docx> <comments.json>` / `suggest <in.docx> <out.docx> <author> <text>` / `accept <in.docx> <out.docx>` / `redline-check <original.docx> <edited.docx>` — add Word comments (paragraph-anchored, threaded replies via `replyTo`), a tracked insertion (reviewer suggestion), accept ALL tracked changes, or PROVE an edited file changed nothing outside tracked changes (run after every redline edit; non-zero exit = an untracked content change slipped in).
26
- - `bin/pdf-form.cjs list <in.pdf>` / `fill <in.pdf> <values.json> <out.pdf> [--flatten]` / `overlay <filled.pdf> <out.pdf>` — inspect/fill PDF forms; `overlay` boxes every field in red for a render-and-look fill check (see pdf.md).
27
- - `bin/pdf-ops.cjs merge <out> <in…>` / `split <in> <from-to> <out>` / `rotate <in> <deg> <out> [pages]` / `watermark <in> <text> <out>` / `stamp <in.pdf> <stamps.json> <out.pdf>` / `encrypt <in> <out> <user-pw> [owner-pw]` / `decrypt <in> <pw> <out>` — PDF page ops, coordinate text stamping onto a flat (non-form) PDF (see pdf.md), and AES-256 password protection.
28
- - `bin/xlsx-recalc.cjs <in.xlsx> <out.xlsx>` — re-evaluate all formulas (HyperFormula) and cache the results, keeping the formulas. Use when a spreadsheet's formula cells show stale/blank values.
29
- - `bin/doc-run.cjs <script.js> [args…]` — run a custom Node script with the engine's libraries bundled in (`require("pptxgenjs"|"docx"|"xlsx"|"exceljs"|"pdf-lib"|"jszip"|"pizzip"|"hyperformula")` guaranteed on any machine; `--list` prints the registry; nothing else is installed). For a **formatted workbook** (fills, font colors, borders, merged cells) use `exceljs` — `xlsx` (SheetJS community) writes no cell styles at all and drops them without an error; `doc-gen xlsx` covers only a bold header, column widths, number formats and formulas. The long-tail escape hatch when no verb fits — see rewrite.md "Custom scripts"; ALWAYS doc-validate the output.
30
- - `bin/html2pptx.cjs <deck.html|snapshot.layout.json> [out.pptx|out.pdf] [--style <id>] [--theme-v2] [--media-style <id>] [--backdrop <variant>] [--editable] [--rasterize] [--preview-dir <dir>] [--preview-pages <all|N,N-M>]` — **THE deck path**: write slide HTML (`<section class="slide" data-layout="…">` on a 1280×720 canvas), Chromium lays it out → NATIVE editable pptx. An `out.pdf` prints the same styled deck instead: one 16:9 vector page per slide, text selectable, links clickable (HTML input only; not with `--editable`/`--rasterize`/`--notes-doc`). `--preview-dir` is a preview-only fast path that writes slide PNGs directly from the styled HTML and skips PPTX/LibreOffice; use all pages once, then `--preview-pages` for changed pages. `--theme-v2` composes the selected family's existing paint with its semantic component profile, default media style and bound art direction; `--media-style` overrides only the media treatment. Entries in a `classic-v1` catalog collection reject both flags; omit them for the frozen V1 path. `--backdrop <variant>` picks a signature backdrop. **DEFAULT = hybrid**: each slide's visuals bake into a full-fidelity raster backdrop with native EDITABLE text on top; auto-falls back to plain native if backdrop capture is unavailable. `--editable` = plain native only (effects flatten). `--rasterize` = full image slides (ZERO editability — tell the user). Advisory warnings on hardcoded brand colors + repeated layouts (P3). Runs in the app's Chromium OR bundled headless Chromium (Playwright) — works off-app (CI/cloud); layout JSON works anywhere. **Tailwind + `size="1".."7"` scale available** (vendored offline). See theme-system.md + deck-design.md + layouts/README.md. `[--theme <theme.json>]` is a separate legacy document recolor path.
31
- - `bin/deck-build.cjs <spec.json> <deck.html>` — **THE FAST deck path** for catalog slides: emit a COMPACT spec with `style`, independent `layoutPack`, and per-slide `intent` + `layout` + content. Modern entries add `themeVersion:2` plus optional `mediaStyle`/`decorRhythm`; frozen `classic-v1` entries must omit those fields and use only pre-V2 layouts. Use `layouts/profiles.json` to match content intent before selecting geometry; under V2 the same intent derives the art scene and deck-level decoration rhythm. The CLI warns on an intent/layout mismatch and errors on an unknown intent. `scenario` (the confirmed scenario) decides whether the family's motion preset is applied — read decks (内审/报告/培训…) get none, `motion: true|false` overrides, and the result JSON reports `motion: {on, reason}` (deck-design.md "Motion"). `layoutPack:"none"` forces shared layouts; omitting `layoutPack` preserves the old behavior where a `kind:"pack"` style implicitly uses its same-name layouts. `slots`, `rows`, and `chart` are still validated against `layouts/slots.json`; any unknown layout/slot/field or missing figure input exits 1 and writes nothing. See theme-system.md for the full schema and compatibility contract.
32
-
33
- All scripts are Node and print a machine-readable result plus a human note. On a
34
- missing dependency or unreadable file they exit non-zero with a clear message —
35
- report that to the user, do not fabricate content.
3
+ Every bin in `bin/`, grouped by task. **Read only your task's section** — Read this
4
+ file with the offset/limit of the line range below; another section only when you call
5
+ a bin from it. Exit codes are shared: `64` bad usage · `65` invalid input · `66`
6
+ unreadable file · `69` a required engine is unavailable · `75` transient, retry · `1` failure.
7
+
8
+ - **Read** (lines 14–22) — doc-scan · doc-text · pdf-render · pdf-images · pdf-tables · ocr · xlsx-query
9
+ - **Generate** (lines 24–28) — doc-gen · doc-run · xlsx-recalc
10
+ - **Fill and edit** (lines 30–44) — doc-fill · doc-xml · docx-annotate · docx-defaults · pptx-clone · pptx-slide · pptx-tokenize · pptx-set-image · pptx-notes · pptx-theme · pptx-embed-fonts · pdf-form · pdf-ops
11
+ - **Decks** (lines 46–58) — deck-scenario · deck-styles · deck-plan · deck-build · deck-chart · html2pptx · deck-qa · template-skin
12
+ - **Validate and convert** (lines 60–63) — doc-validate · doc-convert
13
+
14
+ ## Read
15
+
16
+ - `bin/doc-scan.cjs <file.pdf>` — per-page signals: `{file, pages:[{page, textChars, imageCoverage, mathHint, tableHint, scanned, …}]}`. PDF only. Every PDF reader here (doc-scan, doc-text, pdf-render, pdf-images, pdf-tables) exits 1 on a password-protected PDF — see pdf.md.
17
+ - `bin/doc-text.cjs <file.pdf>` — the PDF's text layer, `[page N]`-delimited. A .docx/.pptx/.xlsx exits 64 (read the sidecar / use xlsx-query).
18
+ - `bin/pdf-render.cjs <file.pdf> <pages|all> [scale]` — render pages to PNG for vision, written beside the PDF as `<stem>_p<N>.png` (prints `{rendered:[{page, path}]}`); `pages` = `3` | `2-5` | `3,7-8` | `all`. `scale` 0.5–4 (default `2` ≈ 144dpi) trades resolution for vision tokens: `1` (≈72dpi, ¼ the tokens) for layout QA; keep `2` when fine print must stay legible (formulas/tables).
19
+ - `bin/pdf-images.cjs <file.pdf> [pages]` — EMBEDDED images at native resolution, written beside the PDF as `<stem>_p<page>_i<index>.png`, plus a manifest `{images:[{page, index, bbox, width, height, path}], skipped}` (`pages` = `N` | `N-M` | `all`, default all). Use this — not a full-page `pdf-render` — when you need the original figures as their own files (e.g. rebuilding a deck). Vector/text-only pages yield nothing → `pdf-render`.
20
+ - `bin/pdf-tables.cjs <file.pdf> [page] [minCols]` — text-layer tables as `{tables:[{page, rows}]}` (`minCols` default 2; rows may carry empty spacer cells). Nothing found, or a ruled/scanned table → render + vision.
21
+ - `bin/ocr.cjs <image> [lang]` — text off a png/jpg/webp/bmp image → `{text, confidence}`; for a scanned PDF `pdf-render` the page first. `lang` is a tesseract code, default `eng`; `chi_sim` = simplified Chinese, `chi_tra` traditional, `+` combines (`chi_sim+eng` for mixed text). `eng` and `chi_sim` ship with the skill and work offline; others download once into `$TALOS_TESSDATA` (no network → exit 1 `OCR failed: …`); `$TALOS_TESSDATA_LANGPATH` names a dir holding every `<lang>.traineddata.gz`. **Prefer vision** when a vision model is available; OCR is the text-only fallback.
22
+ - `bin/xlsx-query.cjs <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]] [--header-row N] [--include-totals]` — exact spreadsheet compute; no mode = `--summary` (sheet names, row/column counts, `headerRow`, headers). `<op>` = `sum` | `avg` | `min` | `max` | `count` | `count-nonempty` (anything else exits 64 listing these); `<col>` is a header label or a 1-based index. The header row is detected (a title row is skipped; `--header-row N` overrides). `--agg` excludes total rows (合计/总计/小计/Total/Subtotal/Sum, or a SUM over the column) and reports them in `excludedRows` (`--include-totals` keeps them), and lists non-numeric cells it skipped in `skippedCells`. `15%` text is 0.15; `¥`/`元` amounts parse. An unknown sheet exits 1 naming the sheets; a column it cannot find returns `value: null, colIdx: -1` — run `--summary` for the exact names. `--sample` prints up to `n` (default 20) rows keyed by header. Caveats: xlsx.md.
23
+
24
+ ## Generate
25
+
26
+ - `bin/doc-gen.cjs <docx|xlsx|pdf> <out> <spec.json> [--theme <theme.json|name>] [--topic <subject> | --palette <id>] [--accent <#rrggbb> | --style <id>] [--no-preview]` — generate documents from scratch; spec shapes in rewrite.md "Generate". DOCX/PDF body fields are exactly `paragraphs` and/or `blocks` (never `sections`); in a PDF, any http(s) URL written in body text, list items or table cells becomes a clickable link and wraps inside the page on its own — never split a URL by hand (there is no other link syntax). Unknown fields and empty bodies exit 65 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. Prints `wrote <out>` (a PDF adds its engine) then one JSON line — the self-check in SKILL.md "After writing a file"; PDF previews land beside it as `<stem>_p<N>.png` (`--no-preview` skips them). A PDF prints in Chromium — the app's service, else a headless Chromium; with neither it exits 69. `doc-gen pptx` exits 64: decks are built with html2pptx. **Look flags** (docx/pdf): `--theme` = a theme file (built-ins `<skill>/themes/<id>.json`) or the name of one the user saved in `$TALOS_DOC_STYLES_DIR/themes`; `--topic <subject>` picks a topic-adaptive palette, `--palette <id>` forces one (`doc-gen --list-palettes`); `--accent <#rrggbb>` / `--style <id>` keep white paper and near-black text with ONE brand colour on H1/H2 + a faint table-header tint — `--style` takes it from a catalog skin or pack (the deck picker's ids; the deck's full palette is NOT carried onto paper); an accent that is not 6-digit hex falls back to black-on-white with a warning. Precedence: `--theme` > spec `"theme"` > `--accent`/`--style` > `--palette`/`--topic` > default.
27
+ - `bin/doc-run.cjs <script.js> [args…]` — run a custom Node script with the engine's libraries bundled in (`require("pptxgenjs"|"docx"|"xlsx"|"exceljs"|"pdf-lib"|"jszip"|"pizzip"|"hyperformula")` guaranteed on any machine; `--list` prints the registry; nothing else is installed, and an unbundled `require` fails naming the bundled alternative). For a **formatted workbook** (fills, font colors, borders, merged cells) use `exceljs` — `xlsx` (SheetJS community) writes no cell styles at all and drops them without an error; `doc-gen xlsx` covers only a bold header, column widths, number formats and formulas. The long-tail escape hatch when no verb fits — see rewrite.md "Custom scripts"; ALWAYS `doc-validate --strict` the output.
28
+ - `bin/xlsx-recalc.cjs <in.xlsx> <out.xlsx>` — re-evaluate every formula (HyperFormula) and cache the results, keeping the formulas (`out` may equal `in`). Prints `{ok, out, recomputed, errors:[{sheet, cell, error}], sample}`; it exits 0 even with formula errors, so `errors` must be read — rewrite.md "Formulas".
29
+
30
+ ## Fill and edit
31
+
32
+ - `bin/doc-fill.cjs <template.docx|pptx|xlsx> <values.json> <out>` — fill `{{placeholders}}` in an existing template, preserving its styling; prints `{ok, out, unfilled}`. A malformed or unclosed tag exits 1 and writes nothing. Syntax: rewrite.md "Fill a template".
33
+ - `bin/doc-xml.cjs replace <in.docx> <out.docx> <find> <replacement>` — cross-run text replace (finds text even when Word split it over runs, keeps formatting; matched within one paragraph; body incl. tables + headers/footers + footnotes/endnotes, not comments); prints `{replaced}` — `0` means the text is not there as written. Try this FIRST for plain text edits. `unpack <file> <dir>` / `pack <dir> <out>` — the raw-XML escape hatch (pretty-printed unpack → edit → repack); always `doc-validate --strict` afterwards. `merge-runs <in.docx> <out.docx>` coalesces fragmented runs before a manual XML edit.
34
+ - `bin/docx-annotate.cjs comment <in.docx> <out.docx> <comments.json>` / `suggest <in.docx> <out.docx> <author> <text>` / `accept <in.docx> <out.docx>` / `redline-check <original.docx> <edited.docx>` — Word review: comments, a tracked insertion, accept ALL tracked changes, and the proof that an edited file changed nothing outside tracked changes (exit 1 + `firstDivergence`). JSON shapes and caveats: rewrite.md "Review".
35
+ - `bin/docx-defaults.cjs <in.docx> <out.docx>` — add the default paragraph style a script-written (docx.js) .docx lacks; without it Pages lays body text out like the heading before it.
36
+ - `bin/pptx-clone.cjs <template.pptx> <slideNo> <out.pptx> [replacements.json]` — clone a styled template slide once per data item (appended at the end), replacing `{{tokens}}` in the clone only (tokens split across runs are still found); prints `{newSlideNumber, replaced, missing, warnings}`. `pptx-clone remove <in> <slideNo> <out>` drops a slide. CAPACITY warnings on stderr mean the text likely overflows its slot — shorten the wording, never shrink the font. See rewrite.md "Template decks".
37
+ - `bin/pptx-slide.cjs add <in.pptx> <out.pptx> [--layout N]` / `dup <in> <slideNo> <out>` / `remove <in> <slideNo> <out>` / `reorder <in> <order> <out>` — slide-lifecycle surgery on an EXISTING deck without a `{{token}}` template: append a blank slide bound to a layout (default: the last slide's), duplicate (appended) or drop a slide, or permute deck order (`reorder in.pptx 3,1,2 out.pptx` — current 1-based positions in the new sequence). Lossless; in-slide TEXT still goes through doc-fill / doc-xml.
38
+ - `bin/pptx-tokenize.cjs <user.pptx> <template.pptx> [schema.json]` — UPLOADED deck → `{{token}}` template keeping its layouts/master/brand, and write `schema.json` of every token. Then `doc-fill` it — fill EVERY token (`original` for slots you keep, else they blank). `leadStyle:true` = lead + body: fill `lead\nbody`. See rewrite.md "Reuse an uploaded deck".
39
+ - `bin/pptx-set-image.cjs <in.pptx> <slideNo> <rId> <image> <out.pptx>` — swap the picture behind a slide's `rId` (pptx-tokenize's image tokens carry it as `rel`), keeping the frame (position/size); prints `{newMedia, oldMedia}`. A different aspect ratio STRETCHES — pre-crop to the slot ratio.
40
+ - `bin/pptx-notes.cjs <in.pptx> <notes.json|{}> <out.pptx>` — speaker notes on an existing deck (`{"1": "…"}` by slide); repairs notes Keynote refuses (`{}`). **Never hand-write notesSlide XML.**
41
+ - `bin/pptx-theme.cjs <ref.pptx> [out.json]` — extract a theme.json (palette/fonts) from an existing deck (printed when no `out.json`). For documents, `doc-gen --theme` it. For a deck, copy its colours into a user skin's tokens (deck-design.md "Custom skins") — `html2pptx --theme` does not repaint a `--style` deck. Palette only, never layouts.
42
+ - `bin/pptx-embed-fonts.cjs <in.pptx> <out.pptx> --font "<Typeface>=<path.ttf>" [--bold …] [--italic …] [--bold-italic …]` — EMBED fonts (OOXML font embedding) so a deck looks the same where the font is missing. `<Typeface>` must match the runs' font name. Full-font embed: SMALL custom/brand fonts only, never big CJK fonts.
43
+ - `bin/pdf-form.cjs list <in.pdf>` / `fill <in.pdf> <values.json> <out.pdf> [--flatten]` / `overlay <filled.pdf> <out.pdf>` — PDF AcroForms: `list` → `{fields:[{name, type}]}`; `fill` takes `{"<text or dropdown field>": "value", "<checkbox>": true}` → `{filled, skipped}` (unknown names are skipped, not errors); `overlay` boxes every field in red for the render-and-look check (pdf.md). Chinese values are drawn in the bundled Noto Sans SC (`--flatten` too); a character it lacks (emoji, rare extension hanzi) exits 1 naming its code point.
44
+ - `bin/pdf-ops.cjs merge <out> <in…>` / `split <in> <from-to> <out>` / `rotate <in> <deg> <out> [pages]` / `watermark <in> <text> <out>` / `stamp <in.pdf> <stamps.json> <out.pdf>` / `encrypt <in> <out> <user-pw> [owner-pw]` / `decrypt <in> <pw> <out>` — page ops that keep every page verbatim (`rotate` pages like `1,3-5`), text stamping onto a flat (non-form) PDF — `stamps.json` = `[{page, x, y, text, size?, grey?}]` in points from the bottom-left (size 11, grey 0 = black, by default) — and AES-256 password protection (a wrong password exits 1). `watermark`/`stamp` draw Helvetica, or the bundled Noto Sans SC when the text has Chinese (same missing-character rule). An encrypted input exits 1 naming `decrypt`; `decrypt <in> "" <out>` handles an owner-password-only PDF.
45
+
46
+ ## Decks
47
+
48
+ Flags and outputs only; the workflow is deck-design.md.
49
+
50
+ - `bin/deck-scenario.cjs "<scenario>"` — the ONE matrix row the clarify gate picked: `{ok, scenario:{id, tone, density, pages, narrative, flows, sections:[{label, key, content}], researchFocus}}`. `content` = what that section must contain (elements, evidence; `;宜` a recommended shape) — it is in no md table. Matches the id, else the longest alias in the text; no match exits 1 with the ids (map to the nearest and rerun).
51
+ - `bin/deck-styles.cjs --scenario "<scenario>"` — the style picker's first round: `{scenario, source, options}`; pass `options` to the picker as printed (SKILL.md "STYLE PICKER"). `source` `mixed`/`fallback` = the scenario claimed under six styles and the default group topped it up.
52
+ - `bin/deck-plan.cjs --layouts --style <id>` — before plan.json: the layouts this style renders and the plan fields each takes (`stats×N` = exactly N, `unit?` optional, `slots.<name>` = pass it, `source ✓` = footer).
53
+ - `bin/deck-plan.cjs <plan.json> [spec.json]` — **content-first layout selection, run BEFORE deck-build.** The plan is the deck spec's top-level fields (`style`, `themeVersion`, `layoutPack`, `scenario`, `density` (read · talk; default from scenario), `footer`, `chartRender`, …) plus per slide its CONTENT: `role` (cover · toc · section · content · ending; default content), optional `intent` (layouts/profiles.json) / `shape` / `layout` (pin), and `title` · `subtitle` (口径 line) · `takeaway` (启示 line) · `eyebrow` · `meta` · `index` · `points` (`[{title, body, date?, label?}]` or strings) · `stats` (`[{value, unit, label, note}]`) · `chart` · `quote` (`{text, source}` or a string) · `table` (`{columns, rows}`) · `image: true` · `source` (one short source line, "IDC,2026" → the footer; dropped with a warning on a layout without one) · `notes` (speaker notes) · `narrative` (outline only) · `file` (a bespoke slide: one `<section class="slide">` in that file, taken as is, `pick:"bespoke"`) · `slots` / `rows` (explicit values, override the draft). A `\n` in any text is a line break. `--outline <slide-outline.md>` also writes the readable outline (one block per slide + its pick) — the approval gate's document, rendered from the plan, never hand-written. Prints `{ok, slides:[{slide, pick, warnings, blanks, alternatives, rejected:[{layout, why}]}], next}`. Fit = the layout's item count (a 3-card layout takes exactly 3 points; rows layouts take 1..their measured ceiling, at least the count the README density column names) + every drafted string within the layout's capacity measured under your `style` (`layouts/capacity.json`; an unknown style uses the tightest skin). Picks under P1/P2/P3 (layouts/README.md). With `spec.json` it writes the drafted deck-build spec (chart slides get `chartRender:"svg"`) — only when EVERY slide got a pick; otherwise exit 1, nothing written: fix the content the `why` names and re-plan. `blanks` = slots the content had nothing for (left empty, not demo text) — fill them or accept. A layout rejected for leaving slots empty names where each one is filled from (`kpi1_desc ← each stat's note`).
54
+ - `bin/deck-build.cjs <spec.json> <deck.html>` — fills catalog layouts from a COMPACT spec (normally the one `deck-plan` drafted): top-level `style`, `layoutPack`, `scenario`, `chartRender`, `capacityCheck`, `motion`, Theme V2 fields (theme-system.md), and per slide `intent` + `layout` + `slots` / `rows` / `chart` / `source` / `notes` (layouts/README.md "How to fill"), or `file` + `notes` for a bespoke slide (relative to the spec, exactly one `<section class="slide">`); a `\n` in a slot is a line break, a typed `<br>` stays text. An unknown layout/slot/field, an unknown intent, a missing figure input, or a chart layout without `chart` / with a chart that has nothing to plot (it would ship the demo chart or a blank box) exits 1 and writes nothing; an intent/layout mismatch warns. **Capacity check** (`layouts/capacity.json`, measured per layout × style): a slot wider than the layout holds, a number that would wrap, more rows than fit, or text longer than its font size may carry (deck-qa's `tiny-font` floor: ≥10pt past 24 characters, ≥8pt up to 24 — a ~9pt footer takes a ≤24-character source line) exits 1 naming the slot and the cut; an un-filled NUMBERED slot (`card3_title` — it ships demo copy), a one-line slot that wraps, or an un-filled unnumbered slot (renders its template text; pass `""` to blank it) warns — treat those warnings as defects. `"capacityCheck":"strict"` makes the un-filled numbered slot fatal too; `"warn"|"off"` downgrades/disables it. **Charts:** `"chartRender":"svg"` (or a chart's own `"render":"svg"`) draws each layout chart from its data as an inline SVG sized to the layout's chart box (see deck-chart above); default `native` keeps the editable `c:chart`. `scenario` decides whether the family's motion preset applies (`motion: true|false` overrides; deck-design.md "Motion"). Prints `{ok, out, slides, warnings, capacity:{checked, mode}, motion:{on, reason}, next}` — `next` is the matching html2pptx command.
55
+ - `bin/deck-chart.cjs <chart.json> [out.svg] --width <px> --height <px>` — a chart drawn from data as an inline `<svg>` for a BESPOKE slide (catalog chart layouts use `chartRender:"svg"` in deck-build instead). `chart.json` = `{type: bar|barH|line|area|pie|doughnut|radar|scatter, labels, series:[{name, values}], stacked?, unit?, highlight?, showValues?, centerLabel?, zeroBaseline?}` (line: axis from 0 instead of zoomed to the data); radar = 3–8 labels, ≤3 series, `max?` (out-of-range clamps + warns); scatter = `series:[{name, points:[[x,y,label?]|{x,y,label}]}]`, `xLabel?`, `yLabel?`, `xUnit?`. Painted only with the skin's CSS variables (re-skins with `--style`); ≤4 series (more exits 1), a 5th+ pie slice folds into 其他; data mismatches warn like the native chart. Without `out.svg` prints the SVG; paste it into a box of exactly W×H px.
56
+ - `bin/html2pptx.cjs <deck.html|snapshot.layout.json> [out.pptx] [--style <id>] [--theme-v2] [--media-style <id>] [--backdrop <variant>] [--line <name|none>] [--editable | --mode native] [--rasterize] [--notes-doc <out.md>] [--preview-dir <dir>] [--preview-pages <all|N,N-M>] [--preview-density <1..4>]` — **THE deck path** (modes, slide contract: deck-design.md). Prints `{ok, out, engine, slides, backdrop, warnings, valid, qa}`; `valid:false` exits 1. Hybrid text a skin bakes into the backdrop (`--talos-bake`) that came out blank there is written native in its place (`healedText`, a warning each — the face may be a fallback); only text it cannot write fails: `ok:false` + `missingText`, exits 1 — never deliver that. `.pdf` output is retired (exit 64): convert the final PPTX with `doc-convert`. `--preview-dir` (no output file) writes slide PNGs straight from the styled HTML, skipping PPTX/LibreOffice — all pages once, then `--preview-pages` for changed ones — read each image once; 1280×720 by default (`--preview-density 2` only for fine print), plus `sheet.png` (all rendered slides as 640×360 tiles — read it first). `--style` resolves a catalog id, then a user style in `$TALOS_DOC_STYLES_DIR/styles/<name>/`, or an explicit directory. `--theme-v2` / `--media-style`: theme-system.md (rejected for `classic-v1` entries). `--backdrop <variant>` picks a signature backdrop (the skin's `backdrop-<variant>.html`, else `styles/_backdrops/<variant>.html`; `default` = the skin's own); without it the deck's variant is picked once at random and remembered beside the HTML (`.html2pptx-backdrop.json`, per style), so every later preview and build of that deck — edited or renamed — keeps it; the result reports it as `backdrop`. `--line` swaps the skin's line motif (`styles/_lines/`). `--backdrop-cache <dir>` (services re-rendering many decks) reuses unchanged hybrid backdrops; `--backdrop-cache-verify` captures anyway and fails on any difference. Advisory warnings: hardcoded brand colours, repeated layouts (P3), and a `data-chart` with nothing to plot — drawn as a dashed "图表占位" box (qa flags it as placeholder text). `[--theme <theme.json>]` is the legacy document-recolor path; a `--style` skin overrides it (a warning says so).
57
+ - `bin/deck-qa.cjs <file.pptx> [--json] [--rendered <office.pdf> | --final [--cache-dir <dir>] [--require-office]]` — MECHANICAL design QA (deterministic, token-free; the authoritative list of lint codes): `off-canvas` (content past the edge), `tiny-font` (<10pt), `shape-overlap` (colliding text), `text-only-slide` (a wall of text with no visual anchor), `low-contrast-text`, `placeholder-text` (a layout never filled — 替换为…/占位/在这里写/Lorem), `font-mismatch` (CJK in a Latin-only font → 豆腐; expected language defaults to the system locale, override with `TALOS_DECK_LANG`). Advisory: exit 0 even with findings. `--json` → `{ok,findings:[{slide,code,message}]}`. `html2pptx` already prints this lint as `qa` after every build. **`--final` is the deck's delivery sign-off in one call**: strict XSD validation (failure exits 1), the lint, an Office render through `doc-convert` (cached in `.office-cache/` beside the deck, or `--cache-dir` / `$TALOS_OFFICE_CACHE_DIR`), `render-overflow` / `render-collision` where Office re-laid the native text past its box, `render-type3-font` + `office.type3Fonts` when the render draws with a Type 3 font (LibreOffice's empty CJK fallback — that text is blank), and `review` + `previews` — PPTX review slides at ≈72dpi (PDF delivery checks every final PDF page; deck-design.md "Verify before delivering"). `--rendered <pdf>` runs the same render check against a PDF you converted yourself; a page count that differs from the deck exits 1.
58
+ - `bin/template-skin.cjs <template.pptx> <name> [--label <显示名>] [--content-page N] [--exclude <key>,…] [--include <key>,…]` — a pptx template → the user's style `$TALOS_DOC_STYLES_DIR/styles/<name>/`. Prints `{style, samples, uncertain, kept, warnings, replaced}`; each `kept` element carries a `hint` (its text, or a picture's corner — "a logo?") for `--exclude <key>`; `--label` = the name the picker shows ("我的风格 · <label>", default the file name); not a catalog id.
59
+
60
+ ## Validate and convert
61
+
62
+ - `bin/doc-validate.cjs <file.docx|pptx|xlsx|pdf> [--strict] [--design-lint]` — structural validation gate: OOXML zip/parts/rels/Content_Types/XML well-formedness, plus pptx `notes-keynote` (→ `pptx-notes <in> {} <out>`) and docx `docx-default-style` (→ `docx-defaults`); a PDF must parse, open without a password (`pdf-encrypted` error: validate before `pdf-ops encrypt`) and hold at least one page (reports `pages` and a `pdf-parse-only` warning — how it looks is render-and-look's job). Exit 1 = broken, fix before delivering. `--strict` (OOXML only; on a PDF it just adds a `strict-not-applicable` warning) adds full ECMA-376 schema (XSD) validation of every recognised part — REQUIRED after custom-code (doc-run) or raw-XML output; a strict failure means the file is non-standard even if Office opens it. `--design-lint` (pptx only) adds deck-qa's lint as advisory warnings (never changes the exit code; codes under deck-qa in Decks) — real design bugs, fix them.
63
+ - `bin/doc-convert.cjs <in.docx|pptx|xlsx> <out.pdf> [--cache-dir <dir>] [--require-office]` — Office → PDF. **PPTX requires Office**: local `soffice`, then `TALOS_OFFICE_URL` sidecar. Missing Office exits 69; failed Office exits 75. Never rebuild a deck as browser HTML. Spreadsheets also require Office. DOCX can fall back to reconstructed HTML via the app's Chromium or headless Chromium (content retained, layout lost); `--require-office` blocks this fallback. Report the `[pdf (engine)]` result and any caveat. `--cache-dir` (or `TALOS_OFFICE_CACHE_DIR`) reuses the local Office result for identical source bytes; after any PPTX edit, convert again. Fonts come from the Office environment, which must have the required faces for faithful typography.