@lotics/cli 0.96.0 → 0.96.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -79374,8 +79374,9 @@ function parseImages(rels, zipEntries) {
79374
79374
  const drawingRelsPath = drawingPath.replace(/([^/]+)$/, "_rels/$1.rels");
79375
79375
  const drawingRelsEntry = findZipEntry(zipEntries, drawingRelsPath);
79376
79376
  const drawingRels = drawingRelsEntry ? parseDrawingRels(decodeUtf8(drawingRelsEntry)) : /* @__PURE__ */ new Map();
79377
- const anchors = wsDr["xdr:twoCellAnchor"];
79378
- if (anchors) {
79377
+ for (const key of ["xdr:twoCellAnchor", "xdr:oneCellAnchor", "xdr:absoluteAnchor"]) {
79378
+ const anchors = wsDr[key];
79379
+ if (!anchors) continue;
79379
79380
  for (const anchor of anchors) {
79380
79381
  if (images.length >= MAX_IMAGES) break;
79381
79382
  const image = extractImageFromAnchor(anchor, drawingRels, zipEntries, drawingPath);
@@ -79401,18 +79402,42 @@ function parseDrawingRels(relsXml2) {
79401
79402
  }
79402
79403
  return map3;
79403
79404
  }
79405
+ var MEDIA_MIME_TYPES = {
79406
+ png: "image/png",
79407
+ jpg: "image/jpeg",
79408
+ jpeg: "image/jpeg",
79409
+ gif: "image/gif",
79410
+ bmp: "image/bmp",
79411
+ tiff: "image/tiff",
79412
+ tif: "image/tiff",
79413
+ svg: "image/svg+xml",
79414
+ emf: "image/x-emf",
79415
+ wmf: "image/x-wmf"
79416
+ };
79417
+ function isSupportedMediaMime(mime) {
79418
+ return Object.values(MEDIA_MIME_TYPES).includes(mime);
79419
+ }
79404
79420
  function extractImageFromAnchor(anchor, drawingRels, zipEntries, drawingPath) {
79405
- const from = anchor["xdr:from"];
79421
+ const from = anchor["xdr:from"] ?? { "xdr:row": "0", "xdr:col": "0" };
79406
79422
  const to = anchor["xdr:to"];
79407
- if (!from || !to) return void 0;
79423
+ const posEl = anchor["xdr:pos"];
79424
+ if (!anchor["xdr:from"] && !posEl) return void 0;
79425
+ const extEl = anchor["xdr:ext"];
79426
+ const extent = extEl ? { cx: parseInt(extEl["@_cx"] ?? "0", 10), cy: parseInt(extEl["@_cy"] ?? "0", 10) } : void 0;
79427
+ const pos = posEl ? { x: parseInt(posEl["@_x"] ?? "0", 10), y: parseInt(posEl["@_y"] ?? "0", 10) } : void 0;
79428
+ const anchorKind = to ? "twoCell" : posEl ? "absolute" : "oneCell";
79408
79429
  const tl = {
79409
79430
  row: parseInt(from["xdr:row"] ?? "0", 10),
79410
- col: parseInt(from["xdr:col"] ?? "0", 10)
79431
+ col: parseInt(from["xdr:col"] ?? "0", 10),
79432
+ rowOff: parseInt(from["xdr:rowOff"] ?? "0", 10),
79433
+ colOff: parseInt(from["xdr:colOff"] ?? "0", 10)
79411
79434
  };
79412
- const br = {
79435
+ const br = to ? {
79413
79436
  row: parseInt(to["xdr:row"] ?? "0", 10) + 1,
79414
- col: parseInt(to["xdr:col"] ?? "0", 10) + 1
79415
- };
79437
+ col: parseInt(to["xdr:col"] ?? "0", 10) + 1,
79438
+ rowOff: parseInt(to["xdr:rowOff"] ?? "0", 10),
79439
+ colOff: parseInt(to["xdr:colOff"] ?? "0", 10)
79440
+ } : { row: tl.row + 1, col: tl.col + 1, rowOff: 0, colOff: 0 };
79416
79441
  const pic = anchor["xdr:pic"];
79417
79442
  if (!pic) return void 0;
79418
79443
  const blipFill = pic["xdr:blipFill"];
@@ -79428,10 +79453,10 @@ function extractImageFromAnchor(anchor, drawingRels, zipEntries, drawingPath) {
79428
79453
  const imageEntry = findZipEntry(zipEntries, imagePath);
79429
79454
  if (!imageEntry) return void 0;
79430
79455
  const ext = imagePath.split(".").pop()?.toLowerCase() ?? "png";
79431
- const mimeType = ext === "jpeg" || ext === "jpg" ? "image/jpeg" : ext === "gif" ? "image/gif" : "image/png";
79456
+ const mimeType = MEDIA_MIME_TYPES[ext] ?? "application/octet-stream";
79432
79457
  const base643 = uint8ToBase64(imageEntry);
79433
79458
  const dataUrl = `data:${mimeType};base64,${base643}`;
79434
- return { dataUrl, tl, br };
79459
+ return { dataUrl, tl, br, ext, anchorKind, extent, pos };
79435
79460
  }
79436
79461
  function parseConditionalFormats(worksheet, theme, indexedColors) {
79437
79462
  const cfArr = worksheet["conditionalFormatting"];
@@ -81897,7 +81922,7 @@ function readWorkbookRels(originalZip) {
81897
81922
  const bytes = originalZip["xl/_rels/workbook.xml.rels"];
81898
81923
  if (!bytes) return out;
81899
81924
  const xml = decode3(bytes);
81900
- const re2 = /<Relationship\b([^/]*?)\/>/g;
81925
+ const re2 = /<Relationship\b([^>]*?)\/>/g;
81901
81926
  let m;
81902
81927
  while ((m = re2.exec(xml)) !== null) {
81903
81928
  const attrs = m[1];
@@ -81931,7 +81956,7 @@ function readSheetPivotPaths(originalZip, sheetTarget) {
81931
81956
  const bytes = originalZip[relsPath];
81932
81957
  if (!bytes) return [];
81933
81958
  const xml = decode3(bytes);
81934
- const re2 = /<Relationship\b([^/]*?)\/>/g;
81959
+ const re2 = /<Relationship\b([^>]*?)\/>/g;
81935
81960
  const out = [];
81936
81961
  let m;
81937
81962
  while ((m = re2.exec(xml)) !== null) {
@@ -81943,6 +81968,32 @@ function readSheetPivotPaths(originalZip, sheetTarget) {
81943
81968
  }
81944
81969
  return out;
81945
81970
  }
81971
+ function extractLegacyHeaderFooterArt(originalZip, sheetTarget) {
81972
+ if (!originalZip || !sheetTarget) return null;
81973
+ const sheetPath = `xl/${sheetTarget}`;
81974
+ const sheetBytes = originalZip[sheetPath];
81975
+ if (!sheetBytes || !/<legacyDrawingHF\b/.test(decode3(sheetBytes))) return null;
81976
+ const relsPath = sheetPath.replace(/([^/]+)$/, "_rels/$1.rels");
81977
+ const relsBytes = originalZip[relsPath];
81978
+ if (!relsBytes) return null;
81979
+ const relsXml2 = decode3(relsBytes);
81980
+ const hfRel = [...relsXml2.matchAll(/<Relationship\b([^>]*?)\/>/g)].map((m) => m[1]).find((attrs) => (/\bType="([^"]+)"/.exec(attrs)?.[1] ?? "").includes("/vmlDrawing"));
81981
+ const target = hfRel ? /\bTarget="([^"]+)"/.exec(hfRel)?.[1] : void 0;
81982
+ if (!target) return null;
81983
+ const vmlPath = `xl/${target.replace(/^\.\.\//, "")}`;
81984
+ if (!originalZip[vmlPath]) return null;
81985
+ const parts = [vmlPath];
81986
+ const vmlRelsPath = vmlPath.replace(/([^/]+)$/, "_rels/$1.rels");
81987
+ const vmlRels = originalZip[vmlRelsPath];
81988
+ if (vmlRels) {
81989
+ parts.push(vmlRelsPath);
81990
+ for (const m of decode3(vmlRels).matchAll(/\bTarget="([^"]+)"/g)) {
81991
+ const mediaPath = `xl/${m[1].replace(/^\.\.\//, "")}`;
81992
+ if (originalZip[mediaPath]) parts.push(mediaPath);
81993
+ }
81994
+ }
81995
+ return { target, parts };
81996
+ }
81946
81997
  function extractPivotRoundTripInfo(originalZip) {
81947
81998
  const empty = {
81948
81999
  cachesByWorkbook: /* @__PURE__ */ new Map(),
@@ -82056,6 +82107,7 @@ function exportWorkbook(workbook, originalZip) {
82056
82107
  regeneratedPaths.add("_rels/.rels");
82057
82108
  regeneratedPaths.add("docProps/core.xml");
82058
82109
  regeneratedPaths.add("docProps/app.xml");
82110
+ regeneratedPaths.add("xl/calcChain.xml");
82059
82111
  for (let i2 = 0; i2 < workbook.sheets.length + 10; i2++) {
82060
82112
  regeneratedPaths.add(`xl/worksheets/sheet${i2 + 1}.xml`);
82061
82113
  regeneratedPaths.add(`xl/worksheets/_rels/sheet${i2 + 1}.xml.rels`);
@@ -82066,7 +82118,10 @@ function exportWorkbook(workbook, originalZip) {
82066
82118
  }
82067
82119
  }
82068
82120
  for (const [path8, data] of Object.entries(originalZip)) {
82069
- if (!regeneratedPaths.has(path8)) entries2[path8] = data;
82121
+ if (regeneratedPaths.has(path8)) continue;
82122
+ const ext = path8.slice(path8.lastIndexOf(".") + 1).toLowerCase();
82123
+ if (!DECLARED_EXTENSIONS.includes(ext)) continue;
82124
+ entries2[path8] = data;
82070
82125
  }
82071
82126
  }
82072
82127
  const sharedStrings = buildSharedStrings(workbook);
@@ -82074,6 +82129,7 @@ function exportWorkbook(workbook, originalZip) {
82074
82129
  const stylesResult = stylesPassthrough ? { xml: "", xfMap: /* @__PURE__ */ new Map(), numFmtMap: /* @__PURE__ */ new Map(), dxfMap: /* @__PURE__ */ new Map() } : buildStylesXml(workbook);
82075
82130
  if (!stylesPassthrough) entries2["xl/styles.xml"] = strToU8(stylesResult.xml);
82076
82131
  const roundTripInfo = extractPivotRoundTripInfo(originalZip);
82132
+ const sheetTargets = originalZip ? readWorkbookSheetOrder(originalZip).map((rid) => readWorkbookRels(originalZip).get(rid)?.target) : [];
82077
82133
  const authoredStartIndex = roundTripInfo.pivotXmlPaths.filter((p) => p.startsWith("xl/pivotTables/")).length + 1;
82078
82134
  const pivotInfo = mergePivotInfo(roundTripInfo, emitAuthoredPivots(workbook, entries2, authoredStartIndex));
82079
82135
  entries2["xl/workbook.xml"] = strToU8(buildWorkbookXml(workbook, pivotInfo));
@@ -82106,6 +82162,15 @@ function exportWorkbook(workbook, originalZip) {
82106
82162
  sheetRels.push(`<Relationship Id="${rId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="${escapeXml(url2)}" TargetMode="External"/>`);
82107
82163
  }
82108
82164
  }
82165
+ let legacyHFRId = "";
82166
+ if (originalZip) {
82167
+ const hf = extractLegacyHeaderFooterArt(originalZip, sheetTargets[i2]);
82168
+ if (hf) {
82169
+ legacyHFRId = `rId${nextRId++}`;
82170
+ sheetRels.push(`<Relationship Id="${legacyHFRId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" Target="${escapeXml(hf.target)}"/>`);
82171
+ for (const part of hf.parts) entries2[part] = originalZip[part];
82172
+ }
82173
+ }
82109
82174
  let drawingRId = "";
82110
82175
  if (needsDrawing) {
82111
82176
  drawingRId = `rId${nextRId++}`;
@@ -82124,7 +82189,8 @@ function exportWorkbook(workbook, originalZip) {
82124
82189
  }
82125
82190
  for (const image of sheet.images) {
82126
82191
  const imgRId = `rId${drawingRelId++}`;
82127
- const ext = getImageExtension(image.dataUrl);
82192
+ const ext = getImageExtension(image.dataUrl, image.ext);
82193
+ while (entries2[`xl/media/image${globalImageIndex}.${ext}`]) globalImageIndex++;
82128
82194
  const imgPath = `xl/media/image${globalImageIndex}.${ext}`;
82129
82195
  const imgBytes = dataUrlToBytes(image.dataUrl);
82130
82196
  if (imgBytes) {
@@ -82183,7 +82249,7 @@ ${sheetRels.join("\n")}
82183
82249
  );
82184
82250
  }
82185
82251
  entries2[`xl/worksheets/sheet${i2 + 1}.xml`] = strToU8(
82186
- buildSheetXml(sheet, workbook.styles, sharedStrings.index, i2 === workbook.activeSheetIndex, stylesResult.xfMap, stylesResult.numFmtMap, stylesResult.dxfMap, drawingRId, tableRIds, hyperlinkRIds)
82252
+ buildSheetXml(sheet, workbook.styles, sharedStrings.index, i2 === workbook.activeSheetIndex, stylesResult.xfMap, stylesResult.numFmtMap, stylesResult.dxfMap, drawingRId, tableRIds, hyperlinkRIds, legacyHFRId)
82187
82253
  );
82188
82254
  }
82189
82255
  entries2["docProps/core.xml"] = strToU8(buildCoreProps());
@@ -82518,7 +82584,7 @@ function extractFillColors(css) {
82518
82584
  }
82519
82585
  return { fg: void 0, bg: void 0 };
82520
82586
  }
82521
- function buildSheetXml(sheet, styles, ssIndex, isActive, xfMap, numFmtMap, dxfMap, drawingRId, tableRIds, hyperlinkRIds) {
82587
+ function buildSheetXml(sheet, styles, ssIndex, isActive, xfMap, numFmtMap, dxfMap, drawingRId, tableRIds, hyperlinkRIds, legacyHFRId) {
82522
82588
  const rows = [];
82523
82589
  const rowMap = /* @__PURE__ */ new Map();
82524
82590
  for (const [ref, cell] of sheet.cells) {
@@ -82568,10 +82634,22 @@ function buildSheetXml(sheet, styles, ssIndex, isActive, xfMap, numFmtMap, dxfMa
82568
82634
  }
82569
82635
  const cols = [];
82570
82636
  const allCols = /* @__PURE__ */ new Set([...sheet.colWidths.keys(), ...sheet.hiddenCols]);
82571
- for (const c of Array.from(allCols).sort((a, b) => a - b)) {
82637
+ const sortedCols = Array.from(allCols).sort((a, b) => a - b);
82638
+ const colDef = (c) => {
82572
82639
  const w = sheet.colWidths.get(c) ?? sheet.defaultColWidth;
82573
82640
  const hidden = sheet.hiddenCols.has(c) ? ' hidden="1"' : "";
82574
- cols.push(`<col min="${c}" max="${c}" width="${w}" customWidth="1"${hidden}/>`);
82641
+ return `width="${w}" customWidth="1"${hidden}`;
82642
+ };
82643
+ for (let i2 = 0; i2 < sortedCols.length; ) {
82644
+ const start = sortedCols[i2];
82645
+ const def = colDef(start);
82646
+ let end = start;
82647
+ while (i2 + 1 < sortedCols.length && sortedCols[i2 + 1] === end + 1 && colDef(sortedCols[i2 + 1]) === def) {
82648
+ end = sortedCols[i2 + 1];
82649
+ i2++;
82650
+ }
82651
+ cols.push(`<col min="${start}" max="${end}" ${def}/>`);
82652
+ i2++;
82575
82653
  }
82576
82654
  let mergeXml = "";
82577
82655
  if (sheet.mergedCells.length > 0) {
@@ -82627,6 +82705,7 @@ function buildSheetXml(sheet, styles, ssIndex, isActive, xfMap, numFmtMap, dxfMa
82627
82705
  hyperlinksXml = `<hyperlinks>${hlEntries}</hyperlinks>`;
82628
82706
  }
82629
82707
  const drawingXml = drawingRId ? `<drawing r:id="${drawingRId}"/>` : "";
82708
+ const legacyHFXml = legacyHFRId ? `<legacyDrawingHF r:id="${legacyHFRId}"/>` : "";
82630
82709
  let tablePartsXml = "";
82631
82710
  if (tableRIds && tableRIds.length > 0) {
82632
82711
  const parts = tableRIds.map((rId) => `<tablePart r:id="${rId}"/>`).join("");
@@ -82697,7 +82776,7 @@ ${cols.length > 0 ? `<cols>${cols.join("")}</cols>` : ""}
82697
82776
  <sheetData>
82698
82777
  ${rows.join("\n")}
82699
82778
  </sheetData>
82700
- ${autoFilterXml}${mergeXml}${cfXml}${dvXml}${hyperlinksXml}${marginsXml}${pageSetupXml}${headerFooterXml}${drawingXml}${tablePartsXml}
82779
+ ${autoFilterXml}${mergeXml}${cfXml}${dvXml}${hyperlinksXml}${marginsXml}${pageSetupXml}${headerFooterXml}${drawingXml}${legacyHFXml}${tablePartsXml}
82701
82780
  </worksheet>`;
82702
82781
  }
82703
82782
  function getCellType(cell, ssIndex) {
@@ -82794,19 +82873,30 @@ function buildWorkbookRels(workbook, pivotInfo) {
82794
82873
  ${rels.join("\n")}
82795
82874
  </Relationships>`;
82796
82875
  }
82876
+ var DECLARED_EXTENSIONS = ["rels", "xml", "png", "jpeg", "jpg", "gif", "vml", "emf", "wmf", "bmp", "tiff", "tif", "svg"];
82797
82877
  function buildContentTypes(sheetCount, extraTypes = []) {
82878
+ const DEFAULT_CONTENT_TYPES = [
82879
+ `<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>`,
82880
+ `<Default Extension="xml" ContentType="application/xml"/>`,
82881
+ `<Default Extension="png" ContentType="image/png"/>`,
82882
+ `<Default Extension="jpeg" ContentType="image/jpeg"/>`,
82883
+ `<Default Extension="jpg" ContentType="image/jpeg"/>`,
82884
+ `<Default Extension="gif" ContentType="image/gif"/>`,
82885
+ `<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>`,
82886
+ `<Default Extension="emf" ContentType="image/x-emf"/>`,
82887
+ `<Default Extension="wmf" ContentType="image/x-wmf"/>`,
82888
+ `<Default Extension="bmp" ContentType="image/bmp"/>`,
82889
+ `<Default Extension="tiff" ContentType="image/tiff"/>`,
82890
+ `<Default Extension="tif" ContentType="image/tiff"/>`,
82891
+ `<Default Extension="svg" ContentType="image/svg+xml"/>`
82892
+ ].join("\n");
82798
82893
  const sheetTypes = Array.from(
82799
82894
  { length: sheetCount },
82800
82895
  (_, i2) => `<Override PartName="/xl/worksheets/sheet${i2 + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`
82801
82896
  ).join("\n");
82802
82897
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
82803
82898
  <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
82804
- <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
82805
- <Default Extension="xml" ContentType="application/xml"/>
82806
- <Default Extension="png" ContentType="image/png"/>
82807
- <Default Extension="jpeg" ContentType="image/jpeg"/>
82808
- <Default Extension="jpg" ContentType="image/jpeg"/>
82809
- <Default Extension="gif" ContentType="image/gif"/>
82899
+ ${DEFAULT_CONTENT_TYPES}
82810
82900
  <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
82811
82901
  ${sheetTypes}
82812
82902
  <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
@@ -82981,15 +83071,37 @@ function buildChartAnchorXml(chart, rId) {
82981
83071
  </xdr:twoCellAnchor>`;
82982
83072
  }
82983
83073
  function buildImageAnchorXml(image, rId) {
82984
- return `<xdr:twoCellAnchor editAs="oneCell">
82985
- <xdr:from>${buildAnchorPosition(image.tl)}</xdr:from>
82986
- <xdr:to>${buildAnchorPosition(image.br)}</xdr:to>
82987
- <xdr:pic>
83074
+ const pic = `<xdr:pic>
82988
83075
  <xdr:nvPicPr><xdr:cNvPr id="0" name="Image"/><xdr:cNvPicPr/></xdr:nvPicPr>
82989
83076
  <xdr:blipFill><a:blip xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:embed="${rId}"/><a:stretch><a:fillRect/></a:stretch></xdr:blipFill>
82990
83077
  <xdr:spPr><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></xdr:spPr>
82991
83078
  </xdr:pic>
82992
- <xdr:clientData/>
83079
+ <xdr:clientData/>`;
83080
+ const from = `<xdr:from>${buildAnchorPosition({ row: image.tl.row, col: image.tl.col, rowOffset: image.tl.rowOff, colOffset: image.tl.colOff })}</xdr:from>`;
83081
+ if (image.anchorKind === "oneCell" && image.extent) {
83082
+ return `<xdr:oneCellAnchor>
83083
+ ${from}
83084
+ <xdr:ext cx="${image.extent.cx}" cy="${image.extent.cy}"/>
83085
+ ${pic}
83086
+ </xdr:oneCellAnchor>`;
83087
+ }
83088
+ if (image.anchorKind === "absolute" && image.extent && image.pos) {
83089
+ return `<xdr:absoluteAnchor>
83090
+ <xdr:pos x="${image.pos.x}" y="${image.pos.y}"/>
83091
+ <xdr:ext cx="${image.extent.cx}" cy="${image.extent.cy}"/>
83092
+ ${pic}
83093
+ </xdr:absoluteAnchor>`;
83094
+ }
83095
+ const to = {
83096
+ row: Math.max(image.tl.row, image.br.row - 1),
83097
+ col: Math.max(image.tl.col, image.br.col - 1),
83098
+ rowOffset: image.br.rowOff,
83099
+ colOffset: image.br.colOff
83100
+ };
83101
+ return `<xdr:twoCellAnchor editAs="oneCell">
83102
+ ${from}
83103
+ <xdr:to>${buildAnchorPosition(to)}</xdr:to>
83104
+ ${pic}
82993
83105
  </xdr:twoCellAnchor>`;
82994
83106
  }
82995
83107
  function buildShapeAnchorXml(drawing) {
@@ -83088,10 +83200,16 @@ function buildDxfXml(style) {
83088
83200
  }
83089
83201
  return `<dxf>${inner}</dxf>`;
83090
83202
  }
83091
- function getImageExtension(dataUrl) {
83203
+ function getImageExtension(dataUrl, sourceExt) {
83204
+ if (sourceExt) return sourceExt.toLowerCase();
83092
83205
  if (dataUrl.startsWith("data:image/png")) return "png";
83093
83206
  if (dataUrl.startsWith("data:image/jpeg") || dataUrl.startsWith("data:image/jpg")) return "jpeg";
83094
83207
  if (dataUrl.startsWith("data:image/gif")) return "gif";
83208
+ if (dataUrl.startsWith("data:image/x-emf")) return "emf";
83209
+ if (dataUrl.startsWith("data:image/x-wmf")) return "wmf";
83210
+ if (dataUrl.startsWith("data:image/bmp")) return "bmp";
83211
+ if (dataUrl.startsWith("data:image/tiff")) return "tiff";
83212
+ if (dataUrl.startsWith("data:image/svg+xml")) return "svg";
83095
83213
  return "png";
83096
83214
  }
83097
83215
  function dataUrlToBytes(dataUrl) {
@@ -83306,6 +83424,46 @@ var SheetModel = class {
83306
83424
  if (!rc) throw new Error(`Invalid cell reference: ${topLeftRef}`);
83307
83425
  this.freeze = { row: rc.row - 1, col: rc.col - 1 };
83308
83426
  }
83427
+ /**
83428
+ * Place a picture on the sheet, anchored to a cell range.
83429
+ *
83430
+ * The authoring counterpart to the parser: without it, adding an image meant
83431
+ * hand-constructing a ParsedImage (0-based anchors, an EXCLUSIVE `br`, a
83432
+ * dataUrl, an anchor kind) — enough hidden convention that every caller would
83433
+ * get one of them wrong. It lives on the model rather than in a tool so that
83434
+ * every surface reaches it: a sandbox code run, a workflow tool, the editor.
83435
+ *
83436
+ * `range` is an ordinary A1 range ("B3:F11"); a single ref ("B3") anchors a
83437
+ * one-cell box. The picture resizes with its cells (twoCell), which is what a
83438
+ * logo in a letterhead should do.
83439
+ *
83440
+ * @param dataUrl `data:image/png;base64,…` — the bytes to embed.
83441
+ * @param range Where it sits, e.g. "B3:F11".
83442
+ */
83443
+ addImage(dataUrl, range2) {
83444
+ const mime = /^data:([^;,]+)[;,]/.exec(dataUrl)?.[1];
83445
+ if (!mime) throw new Error("addImage expects a data: URL, e.g. data:image/png;base64,\u2026");
83446
+ if (!isSupportedMediaMime(mime)) {
83447
+ throw new Error(`addImage cannot embed ${mime} \u2014 xl/media holds png, jpeg, gif, bmp, tiff, svg, emf or wmf`);
83448
+ }
83449
+ const [startRef, endRef] = range2.split(":");
83450
+ const start = refToRowCol(startRef ?? "");
83451
+ const end = endRef ? refToRowCol(endRef) : start;
83452
+ if (!start || !end) throw new Error(`Invalid cell range: ${range2}`);
83453
+ const image = {
83454
+ dataUrl,
83455
+ // refToRowCol is 1-based; anchors are 0-based, and `br` is EXCLUSIVE — one
83456
+ // past the last covered cell — so the end ref converts straight across
83457
+ // while the start ref loses one.
83458
+ // rowOff/colOff are pinned to 0 (not left undefined) so an authored image
83459
+ // is structurally identical to a parsed one — every reader handles one shape.
83460
+ tl: { row: Math.min(start.row, end.row) - 1, col: Math.min(start.col, end.col) - 1, rowOff: 0, colOff: 0 },
83461
+ br: { row: Math.max(start.row, end.row), col: Math.max(start.col, end.col), rowOff: 0, colOff: 0 },
83462
+ anchorKind: "twoCell"
83463
+ };
83464
+ this.images.push(image);
83465
+ return image;
83466
+ }
83309
83467
  /** Get cell, returning undefined for empty cells. */
83310
83468
  getCell(ref) {
83311
83469
  return this.cells.get(ref);
@@ -76,8 +76,15 @@ format handles display.
76
76
  ### Word (`create_word_template`)
77
77
 
78
78
  1. Build a `.docx` with `{{marker}}`s where values go. `lotics upload ./template.docx`.
79
- 2. `create_word_template` with the `template_file_id` and `variables`. As with Excel, an
80
- unmarked declared variable comes back in `unmarked_variables` and is skipped.
79
+ 2. `create_word_template` with the `template_file_id`. `variables` is optional every
80
+ marker in the document is derived automatically (`{{name}}` string,
81
+ `{{FOR … IN name}}` → array, `{{IF name}}` → boolean). When you do declare, an unmarked
82
+ declaration comes back in `unmarked_variables` and is skipped, and markers you didn't
83
+ declare are still auto-added (`derived_variables`) — the stored contract always covers
84
+ every marker in the document.
85
+
86
+ At generate time, `data` must provide a key for **every** marker — pass `""` for fields
87
+ that should render blank; a missing key fails with the full list of missing markers.
81
88
 
82
89
  To add markers/loops/conditionals to the uploaded file from the CLI: `word_replace_text`,
83
90
  `word_insert_loop`, `word_insert_conditional` (and their `remove_*` counterparts).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.96.0",
3
+ "version": "0.96.2",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {