@lotics/cli 0.96.5 → 0.97.0

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/README.md CHANGED
@@ -92,11 +92,14 @@ Every command names its target before it acts — `lotics → <org> / <workspace
92
92
 
93
93
  ```
94
94
  --api-key flag > LOTICS_API_KEY env > LOTICS_ORG env (name|id)
95
- > local .lotics/config.json > global active profile
95
+ > local .lotics/config.json > app manifest workspace → its saved profile
96
+ > global active profile
96
97
  ```
97
98
 
98
99
  `LOTICS_WORKSPACE` (or `--workspace <id>` / `-w`) overrides the workspace at any level. For ephemeral or CI use, set `LOTICS_API_KEY` instead of saving anything.
99
100
 
101
+ In an app project, `lotics app *` commands derive the credential from the directory when nothing explicit chose one: the manifest names the app's workspace, and when exactly one saved profile owns that workspace, that profile is used — the machine-wide default (which another shell can move between two of your commands) is never consulted. An explicit flag, env var, or directory pin still wins, and an org whose profile remembers a different workspace simply falls through to the announced default, exactly as before.
102
+
100
103
  ## Workspaces
101
104
 
102
105
  Workspaces live inside the active org. If the org has more than one, select before running tools:
@@ -52872,8 +52872,9 @@ ${e.toString()}`);
52872
52872
  const max2 = parseInt(col["@_max"] ?? "1", 10);
52873
52873
  const width = col["@_width"] ? parseFloat(col["@_width"]) : defaultColWidth;
52874
52874
  const hidden = col["@_hidden"] === "1" || col["@_hidden"] === "true";
52875
+ const customWidth = col["@_customWidth"] === "1" || col["@_customWidth"] === "true";
52875
52876
  const outlineLevel = parseInt(col["@_outlineLevel"] ?? "0", 10);
52876
- columns.push({ min, max: max2, width, hidden, outlineLevel });
52877
+ columns.push({ min, max: max2, width, hidden, customWidth, outlineLevel });
52877
52878
  for (let c = min; c <= max2; c++) {
52878
52879
  if (hidden) colHidden.add(c);
52879
52880
  if (outlineLevel > 0) colOutlineLevels.set(c, outlineLevel);
@@ -52883,9 +52884,11 @@ ${e.toString()}`);
52883
52884
  }
52884
52885
  function buildColumns(colDefs, colHidden, maxContentWidth, colCount, _defaultColWidth) {
52885
52886
  const explicitWidth = /* @__PURE__ */ new Map();
52887
+ const pinned = /* @__PURE__ */ new Set();
52886
52888
  for (const def of colDefs) {
52887
52889
  for (let c = def.min; c <= def.max; c++) {
52888
52890
  explicitWidth.set(c, def.width);
52891
+ if (def.customWidth) pinned.add(c);
52889
52892
  }
52890
52893
  }
52891
52894
  const columns = [];
@@ -52899,7 +52902,7 @@ ${e.toString()}`);
52899
52902
  } else {
52900
52903
  width = Math.max(8, Math.min(contentChars + 2, 40));
52901
52904
  }
52902
- columns.push({ index: c, width, hidden });
52905
+ columns.push({ index: c, width, hidden, ...pinned.has(c) ? { customWidth: true } : {} });
52903
52906
  }
52904
52907
  return columns;
52905
52908
  }
@@ -52943,6 +52946,7 @@ ${e.toString()}`);
52943
52946
  rowCount++;
52944
52947
  if (rowCount > maxRows) continue;
52945
52948
  const height = rowEl["@_ht"] ? parseFloat(rowEl["@_ht"]) : defaultRowHeight;
52949
+ const customHeight = rowEl["@_customHeight"] === "1" || rowEl["@_customHeight"] === "true";
52946
52950
  const cellArr = rowEl["c"];
52947
52951
  const cells = [];
52948
52952
  if (cellArr) {
@@ -52964,7 +52968,8 @@ ${e.toString()}`);
52964
52968
  index: rowIndex,
52965
52969
  height,
52966
52970
  cells,
52967
- hidden: rowHidden
52971
+ hidden: rowHidden,
52972
+ ...customHeight ? { customHeight: true } : {}
52968
52973
  });
52969
52974
  }
52970
52975
  return {
@@ -54378,7 +54383,7 @@ ${e.toString()}`);
54378
54383
  sheet.freeze = { row: ps.freezePane.frozenRows, col: ps.freezePane.frozenCols };
54379
54384
  }
54380
54385
  for (const col of ps.columns) {
54381
- if (col.width !== ps.defaultColWidth) {
54386
+ if (col.customWidth || col.width !== ps.defaultColWidth) {
54382
54387
  sheet.colWidths.set(col.index, col.width);
54383
54388
  }
54384
54389
  if (col.hidden) {
@@ -54391,7 +54396,7 @@ ${e.toString()}`);
54391
54396
  return `${startRef}:${endRef}`;
54392
54397
  });
54393
54398
  for (const row of ps.rows) {
54394
- if (row.height !== ps.defaultRowHeight) {
54399
+ if (row.customHeight || row.height !== ps.defaultRowHeight) {
54395
54400
  sheet.rowHeights.set(row.index, row.height);
54396
54401
  }
54397
54402
  if (row.hidden) {
package/dist/src/cli.js CHANGED
@@ -45107,7 +45107,7 @@ function resolveProfileByNameOrId(profiles, nameOrId) {
45107
45107
  }
45108
45108
  return null;
45109
45109
  }
45110
- function resolveContext(flags) {
45110
+ function resolveContext(flags, appWorkspaceId) {
45111
45111
  const envKey = process.env.LOTICS_API_KEY;
45112
45112
  const envOrg = process.env.LOTICS_ORG;
45113
45113
  const envWorkspace = process.env.LOTICS_WORKSPACE;
@@ -45157,6 +45157,19 @@ function resolveContext(flags) {
45157
45157
  `This directory's .lotics/config.json uses the old self-contained format (inline key), which is no longer supported. Re-pin with "lotics auth api-key <key> --local" (or "lotics org use <name|id> --local").`
45158
45158
  );
45159
45159
  }
45160
+ if (appWorkspaceId && !wsOverride) {
45161
+ const matches = Object.entries(profiles).filter(([, p]) => p.workspace_id === appWorkspaceId);
45162
+ if (matches.length === 1) {
45163
+ const [orgId, profile] = matches[0];
45164
+ return {
45165
+ apiKey: profile.api_key,
45166
+ orgId,
45167
+ orgName: profile.org_name,
45168
+ workspaceId: appWorkspaceId,
45169
+ source: "app_manifest_profile"
45170
+ };
45171
+ }
45172
+ }
45160
45173
  if (global2?.active_org) {
45161
45174
  const profile = profiles[global2.active_org];
45162
45175
  if (!profile) {
@@ -79139,8 +79152,9 @@ function parseCols(worksheet, defaultColWidth) {
79139
79152
  const max3 = parseInt(col["@_max"] ?? "1", 10);
79140
79153
  const width = col["@_width"] ? parseFloat(col["@_width"]) : defaultColWidth;
79141
79154
  const hidden = col["@_hidden"] === "1" || col["@_hidden"] === "true";
79155
+ const customWidth = col["@_customWidth"] === "1" || col["@_customWidth"] === "true";
79142
79156
  const outlineLevel = parseInt(col["@_outlineLevel"] ?? "0", 10);
79143
- columns.push({ min: min2, max: max3, width, hidden, outlineLevel });
79157
+ columns.push({ min: min2, max: max3, width, hidden, customWidth, outlineLevel });
79144
79158
  for (let c = min2; c <= max3; c++) {
79145
79159
  if (hidden) colHidden.add(c);
79146
79160
  if (outlineLevel > 0) colOutlineLevels.set(c, outlineLevel);
@@ -79150,9 +79164,11 @@ function parseCols(worksheet, defaultColWidth) {
79150
79164
  }
79151
79165
  function buildColumns(colDefs, colHidden, maxContentWidth, colCount, _defaultColWidth) {
79152
79166
  const explicitWidth = /* @__PURE__ */ new Map();
79167
+ const pinned = /* @__PURE__ */ new Set();
79153
79168
  for (const def of colDefs) {
79154
79169
  for (let c = def.min; c <= def.max; c++) {
79155
79170
  explicitWidth.set(c, def.width);
79171
+ if (def.customWidth) pinned.add(c);
79156
79172
  }
79157
79173
  }
79158
79174
  const columns = [];
@@ -79166,7 +79182,7 @@ function buildColumns(colDefs, colHidden, maxContentWidth, colCount, _defaultCol
79166
79182
  } else {
79167
79183
  width = Math.max(8, Math.min(contentChars + 2, 40));
79168
79184
  }
79169
- columns.push({ index: c, width, hidden });
79185
+ columns.push({ index: c, width, hidden, ...pinned.has(c) ? { customWidth: true } : {} });
79170
79186
  }
79171
79187
  return columns;
79172
79188
  }
@@ -79210,6 +79226,7 @@ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultR
79210
79226
  rowCount++;
79211
79227
  if (rowCount > maxRows) continue;
79212
79228
  const height = rowEl["@_ht"] ? parseFloat(rowEl["@_ht"]) : defaultRowHeight;
79229
+ const customHeight = rowEl["@_customHeight"] === "1" || rowEl["@_customHeight"] === "true";
79213
79230
  const cellArr = rowEl["c"];
79214
79231
  const cells = [];
79215
79232
  if (cellArr) {
@@ -79231,7 +79248,8 @@ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultR
79231
79248
  index: rowIndex,
79232
79249
  height,
79233
79250
  cells,
79234
- hidden: rowHidden
79251
+ hidden: rowHidden,
79252
+ ...customHeight ? { customHeight: true } : {}
79235
79253
  });
79236
79254
  }
79237
79255
  return {
@@ -83767,7 +83785,7 @@ function loadSheetFromParsed(ps, styles) {
83767
83785
  sheet.freeze = { row: ps.freezePane.frozenRows, col: ps.freezePane.frozenCols };
83768
83786
  }
83769
83787
  for (const col of ps.columns) {
83770
- if (col.width !== ps.defaultColWidth) {
83788
+ if (col.customWidth || col.width !== ps.defaultColWidth) {
83771
83789
  sheet.colWidths.set(col.index, col.width);
83772
83790
  }
83773
83791
  if (col.hidden) {
@@ -83780,7 +83798,7 @@ function loadSheetFromParsed(ps, styles) {
83780
83798
  return `${startRef}:${endRef}`;
83781
83799
  });
83782
83800
  for (const row of ps.rows) {
83783
- if (row.height !== ps.defaultRowHeight) {
83801
+ if (row.customHeight || row.height !== ps.defaultRowHeight) {
83784
83802
  sheet.rowHeights.set(row.index, row.height);
83785
83803
  }
83786
83804
  if (row.hidden) {
@@ -84668,6 +84686,103 @@ function clampInertCells(sheet) {
84668
84686
  for (const ref of pruned.keys()) sheet.cells.delete(ref);
84669
84687
  return pruned;
84670
84688
  }
84689
+ function axisStores(sheet, axis) {
84690
+ return axis === "row" ? {
84691
+ sizes: sheet.rowHeights,
84692
+ outline: sheet.rowOutlineLevels,
84693
+ hidden: sheet.hiddenRows,
84694
+ breaks: sheet.rowPageBreaks,
84695
+ setBreaks: (next) => {
84696
+ sheet.rowPageBreaks = next;
84697
+ }
84698
+ } : {
84699
+ sizes: sheet.colWidths,
84700
+ outline: sheet.colOutlineLevels,
84701
+ hidden: sheet.hiddenCols,
84702
+ breaks: sheet.colPageBreaks,
84703
+ setBreaks: (next) => {
84704
+ sheet.colPageBreaks = next;
84705
+ }
84706
+ };
84707
+ }
84708
+ function shiftedIndex(i2, at2, delta) {
84709
+ if (i2 < at2) return i2;
84710
+ if (delta < 0 && i2 < at2 - delta) return null;
84711
+ return i2 + delta;
84712
+ }
84713
+ function shiftKeys(map3, at2, delta) {
84714
+ const affected = [...map3.entries()].filter(([i2]) => i2 >= at2);
84715
+ for (const [i2] of affected) map3.delete(i2);
84716
+ for (const [i2, value] of affected) {
84717
+ const next = shiftedIndex(i2, at2, delta);
84718
+ if (next !== null) map3.set(next, value);
84719
+ }
84720
+ }
84721
+ function shiftMembers(set2, at2, delta) {
84722
+ const affected = [...set2].filter((i2) => i2 >= at2);
84723
+ for (const i2 of affected) set2.delete(i2);
84724
+ for (const i2 of affected) {
84725
+ const next = shiftedIndex(i2, at2, delta);
84726
+ if (next !== null) set2.add(next);
84727
+ }
84728
+ }
84729
+ function shiftBreaks(breaks, at2, delta) {
84730
+ const moved = breaks.map((i2) => shiftedIndex(i2, at2, delta)).filter((i2) => i2 !== null);
84731
+ return [...new Set(moved)].sort((a, b) => a - b);
84732
+ }
84733
+ function remapHyperlinks(sheet, landing) {
84734
+ const next = /* @__PURE__ */ new Map();
84735
+ for (const [ref, url2] of sheet.hyperlinks) {
84736
+ const rc = refToRowColSafe(ref);
84737
+ if (!rc) {
84738
+ next.set(ref, url2);
84739
+ continue;
84740
+ }
84741
+ const to = landing(rc.row, rc.col);
84742
+ if (to) next.set(rowColToRef(to.row, to.col), url2);
84743
+ }
84744
+ sheet.hyperlinks = next;
84745
+ }
84746
+ function axisLanding(axis, at2, delta) {
84747
+ return (row, col) => {
84748
+ const next = shiftedIndex(axis === "row" ? row : col, at2, delta);
84749
+ if (next === null) return null;
84750
+ return axis === "row" ? { row: next, col } : { row, col: next };
84751
+ };
84752
+ }
84753
+ function shiftAxisMetadata(sheet, axis, at2, delta) {
84754
+ const stores = axisStores(sheet, axis);
84755
+ shiftKeys(stores.sizes, at2, delta);
84756
+ shiftKeys(stores.outline, at2, delta);
84757
+ shiftMembers(stores.hidden, at2, delta);
84758
+ stores.setBreaks(shiftBreaks(stores.breaks, at2, delta));
84759
+ remapHyperlinks(sheet, axisLanding(axis, at2, delta));
84760
+ }
84761
+ var EMPTY_BAND = { sizes: [], outline: [], hidden: [], breaks: [], hyperlinks: [] };
84762
+ function snapshotAxisBand(sheet, axis, at2, count) {
84763
+ const stores = axisStores(sheet, axis);
84764
+ const inBand = (i2) => i2 >= at2 && i2 < at2 + count;
84765
+ const links = [];
84766
+ for (const [ref, url2] of sheet.hyperlinks) {
84767
+ const rc = refToRowColSafe(ref);
84768
+ if (rc && inBand(axis === "row" ? rc.row : rc.col)) links.push([ref, url2]);
84769
+ }
84770
+ return {
84771
+ sizes: [...stores.sizes.entries()].filter(([i2]) => inBand(i2)),
84772
+ outline: [...stores.outline.entries()].filter(([i2]) => inBand(i2)),
84773
+ hidden: [...stores.hidden].filter(inBand),
84774
+ breaks: stores.breaks.filter(inBand),
84775
+ hyperlinks: links
84776
+ };
84777
+ }
84778
+ function restoreAxisBand(sheet, axis, band) {
84779
+ const stores = axisStores(sheet, axis);
84780
+ for (const [i2, value] of band.sizes) stores.sizes.set(i2, value);
84781
+ for (const [i2, value] of band.outline) stores.outline.set(i2, value);
84782
+ for (const i2 of band.hidden) stores.hidden.add(i2);
84783
+ stores.setBreaks([.../* @__PURE__ */ new Set([...stores.breaks, ...band.breaks])].sort((a, b) => a - b));
84784
+ for (const [ref, url2] of band.hyperlinks) sheet.hyperlinks.set(ref, url2);
84785
+ }
84671
84786
  function insertRowsCommand(workbook, sheetIndex, at2, count) {
84672
84787
  const sheet = workbook.sheets[sheetIndex];
84673
84788
  let mergeSnapshot = [];
@@ -84688,11 +84803,7 @@ function insertRowsCommand(workbook, sheetIndex, at2, count) {
84688
84803
  sheet.cells.delete(ref);
84689
84804
  sheet.cells.set(rowColToRef(row + count, col), cell);
84690
84805
  }
84691
- const heightsToShift = Array.from(sheet.rowHeights.entries()).filter(([r]) => r >= at2).sort((a, b) => b[0] - a[0]);
84692
- for (const [r, h] of heightsToShift) {
84693
- sheet.rowHeights.delete(r);
84694
- sheet.rowHeights.set(r + count, h);
84695
- }
84806
+ shiftAxisMetadata(sheet, "row", at2, count);
84696
84807
  adjustAllFormulasForRowShift(sheet, at2, count);
84697
84808
  adjustMergedCellsForRowInsert(sheet, at2, count);
84698
84809
  workbook.emit({ type: "rows_inserted", sheet: sheetIndex, at: at2, count });
@@ -84714,12 +84825,7 @@ function insertRowsCommand(workbook, sheetIndex, at2, count) {
84714
84825
  sheet.cells.delete(ref);
84715
84826
  sheet.cells.set(rowColToRef(row - count, col), cell);
84716
84827
  }
84717
- const heightsToShift = Array.from(sheet.rowHeights.entries()).filter(([r]) => r >= at2 + count).sort((a, b) => a[0] - b[0]);
84718
- for (const [r, h] of heightsToShift) {
84719
- sheet.rowHeights.delete(r);
84720
- sheet.rowHeights.set(r - count, h);
84721
- }
84722
- for (let r = at2; r < at2 + count; r++) sheet.rowHeights.delete(r);
84828
+ shiftAxisMetadata(sheet, "row", at2, -count);
84723
84829
  for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
84724
84830
  workbook.emit({ type: "rows_deleted", sheet: sheetIndex, at: at2, count });
84725
84831
  }
@@ -84736,11 +84842,13 @@ function deleteRowsCommand(workbook, sheetIndex, at2, count) {
84736
84842
  }
84737
84843
  let mergeSnapshot = [];
84738
84844
  let inertSnapshot = /* @__PURE__ */ new Map();
84845
+ let bandSnapshot = EMPTY_BAND;
84739
84846
  return {
84740
84847
  description: `Delete ${count} row${count > 1 ? "s" : ""} at ${at2}`,
84741
84848
  execute() {
84742
84849
  mergeSnapshot = [...sheet.mergedCells];
84743
84850
  inertSnapshot = clampInertCells(sheet);
84851
+ bandSnapshot = snapshotAxisBand(sheet, "row", at2, count);
84744
84852
  for (const ref of snapshot.keys()) sheet.cells.delete(ref);
84745
84853
  const toMove = [];
84746
84854
  for (const [ref] of sheet.cells) {
@@ -84753,12 +84861,15 @@ function deleteRowsCommand(workbook, sheetIndex, at2, count) {
84753
84861
  sheet.cells.delete(ref);
84754
84862
  sheet.cells.set(rowColToRef(row - count, col), cell);
84755
84863
  }
84864
+ shiftAxisMetadata(sheet, "row", at2, -count);
84756
84865
  adjustAllFormulasForRowShift(sheet, at2, -count);
84757
84866
  adjustMergedCellsForRowDelete(sheet, at2, count);
84758
84867
  workbook.emit({ type: "rows_deleted", sheet: sheetIndex, at: at2, count });
84759
84868
  },
84760
84869
  undo() {
84761
84870
  sheet.mergedCells = [...mergeSnapshot];
84871
+ shiftAxisMetadata(sheet, "row", at2, count);
84872
+ restoreAxisBand(sheet, "row", bandSnapshot);
84762
84873
  const toMove = [];
84763
84874
  for (const [ref] of sheet.cells) {
84764
84875
  const rc = refToRowColSafe(ref);
@@ -84798,11 +84909,7 @@ function insertColsCommand(workbook, sheetIndex, at2, count) {
84798
84909
  sheet.cells.delete(ref);
84799
84910
  sheet.cells.set(rowColToRef(row, col + count), cell);
84800
84911
  }
84801
- const widthsToShift = Array.from(sheet.colWidths.entries()).filter(([c]) => c >= at2).sort((a, b) => b[0] - a[0]);
84802
- for (const [c, w] of widthsToShift) {
84803
- sheet.colWidths.delete(c);
84804
- sheet.colWidths.set(c + count, w);
84805
- }
84912
+ shiftAxisMetadata(sheet, "col", at2, count);
84806
84913
  adjustAllFormulasForColShift(sheet, at2, count);
84807
84914
  adjustMergedCellsForColInsert(sheet, at2, count);
84808
84915
  workbook.emit({ type: "cols_inserted", sheet: sheetIndex, at: at2, count });
@@ -84824,12 +84931,7 @@ function insertColsCommand(workbook, sheetIndex, at2, count) {
84824
84931
  sheet.cells.delete(ref);
84825
84932
  sheet.cells.set(rowColToRef(row, col - count), cell);
84826
84933
  }
84827
- const widthsToShift = Array.from(sheet.colWidths.entries()).filter(([c]) => c >= at2 + count).sort((a, b) => a[0] - b[0]);
84828
- for (const [c, w] of widthsToShift) {
84829
- sheet.colWidths.delete(c);
84830
- sheet.colWidths.set(c - count, w);
84831
- }
84832
- for (let c = at2; c < at2 + count; c++) sheet.colWidths.delete(c);
84934
+ shiftAxisMetadata(sheet, "col", at2, -count);
84833
84935
  for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
84834
84936
  workbook.emit({ type: "cols_deleted", sheet: sheetIndex, at: at2, count });
84835
84937
  }
@@ -84846,11 +84948,13 @@ function deleteColsCommand(workbook, sheetIndex, at2, count) {
84846
84948
  }
84847
84949
  let mergeSnapshot = [];
84848
84950
  let inertSnapshot = /* @__PURE__ */ new Map();
84951
+ let bandSnapshot = EMPTY_BAND;
84849
84952
  return {
84850
84953
  description: `Delete ${count} column${count > 1 ? "s" : ""} at ${at2}`,
84851
84954
  execute() {
84852
84955
  mergeSnapshot = [...sheet.mergedCells];
84853
84956
  inertSnapshot = clampInertCells(sheet);
84957
+ bandSnapshot = snapshotAxisBand(sheet, "col", at2, count);
84854
84958
  for (const ref of snapshot.keys()) sheet.cells.delete(ref);
84855
84959
  const toMove = [];
84856
84960
  for (const [ref] of sheet.cells) {
@@ -84863,12 +84967,15 @@ function deleteColsCommand(workbook, sheetIndex, at2, count) {
84863
84967
  sheet.cells.delete(ref);
84864
84968
  sheet.cells.set(rowColToRef(row, col - count), cell);
84865
84969
  }
84970
+ shiftAxisMetadata(sheet, "col", at2, -count);
84866
84971
  adjustAllFormulasForColShift(sheet, at2, -count);
84867
84972
  adjustMergedCellsForColDelete(sheet, at2, count);
84868
84973
  workbook.emit({ type: "cols_deleted", sheet: sheetIndex, at: at2, count });
84869
84974
  },
84870
84975
  undo() {
84871
84976
  sheet.mergedCells = [...mergeSnapshot];
84977
+ shiftAxisMetadata(sheet, "col", at2, count);
84978
+ restoreAxisBand(sheet, "col", bandSnapshot);
84872
84979
  const toMove = [];
84873
84980
  for (const [ref] of sheet.cells) {
84874
84981
  const rc = refToRowColSafe(ref);
@@ -90252,6 +90359,170 @@ async function ensureContentTypeOverride(doc, partName, contentType) {
90252
90359
  doc.zip.file("[Content_Types].xml", updated);
90253
90360
  }
90254
90361
 
90362
+ // ../ooxml/src/queries.ts
90363
+ function getBodyElementType(el) {
90364
+ const tag = getTagName(el);
90365
+ if (tag === "w:p") return "paragraph";
90366
+ if (tag === "w:tbl") return "table";
90367
+ return "other";
90368
+ }
90369
+ function extractParagraphText(el) {
90370
+ const tag = getTagName(el);
90371
+ if (tag !== "w:p") return "";
90372
+ const parts = [];
90373
+ for (const child of getChildren(el)) {
90374
+ const childTag = getTagName(child);
90375
+ if (childTag === "w:r") {
90376
+ for (const runChild of getChildren(child)) {
90377
+ if (getTagName(runChild) === "w:t") {
90378
+ parts.push(getTextContent(runChild));
90379
+ }
90380
+ }
90381
+ } else if (childTag === "w:hyperlink") {
90382
+ for (const hlChild of getChildren(child)) {
90383
+ if (getTagName(hlChild) === "w:r") {
90384
+ for (const runChild of getChildren(hlChild)) {
90385
+ if (getTagName(runChild) === "w:t") {
90386
+ parts.push(getTextContent(runChild));
90387
+ }
90388
+ }
90389
+ }
90390
+ }
90391
+ }
90392
+ }
90393
+ return parts.join("");
90394
+ }
90395
+ function getRunsFromParagraph(el) {
90396
+ const runs = [];
90397
+ for (const child of getChildren(el)) {
90398
+ if (getTagName(child) !== "w:r") continue;
90399
+ let text = "";
90400
+ let textIdx = -1;
90401
+ const children = getChildren(child);
90402
+ for (let i2 = 0; i2 < children.length; i2++) {
90403
+ if (getTagName(children[i2]) === "w:t") {
90404
+ const tChildren = getChildren(children[i2]);
90405
+ for (const tn of tChildren) {
90406
+ if (typeof tn["#text"] === "string") {
90407
+ text += tn["#text"];
90408
+ }
90409
+ }
90410
+ textIdx = i2;
90411
+ }
90412
+ }
90413
+ if (textIdx >= 0) {
90414
+ runs.push({ element: child, text, textNodeIndex: textIdx });
90415
+ }
90416
+ }
90417
+ return runs;
90418
+ }
90419
+ function setRunText(run, newText) {
90420
+ const children = getChildren(run.element);
90421
+ children[run.textNodeIndex] = {
90422
+ "w:t": [{ "#text": newText }],
90423
+ ":@": { "@_xml:space": "preserve" }
90424
+ };
90425
+ run.text = newText;
90426
+ }
90427
+ function computeReplacementRanges(text, query, replacement, matchType, maxReplacements, startCount) {
90428
+ const ranges = [];
90429
+ let count = startCount;
90430
+ if (matchType === "exact") {
90431
+ if (text === query && count < maxReplacements) {
90432
+ ranges.push({ start: 0, end: text.length, replacement });
90433
+ count++;
90434
+ }
90435
+ return { ranges, count };
90436
+ }
90437
+ if (matchType === "regex") {
90438
+ const re2 = new RegExp(query, "gi");
90439
+ let match2;
90440
+ while (count < maxReplacements && (match2 = re2.exec(text)) !== null) {
90441
+ ranges.push({ start: match2.index, end: match2.index + match2[0].length, replacement });
90442
+ count++;
90443
+ if (match2[0].length === 0) re2.lastIndex++;
90444
+ }
90445
+ return { ranges, count };
90446
+ }
90447
+ if (query.length === 0) return { ranges, count };
90448
+ const lower2 = text.toLowerCase();
90449
+ const lowerQuery = query.toLowerCase();
90450
+ let searchFrom = 0;
90451
+ while (count < maxReplacements) {
90452
+ const idx = lower2.indexOf(lowerQuery, searchFrom);
90453
+ if (idx === -1) break;
90454
+ ranges.push({ start: idx, end: idx + query.length, replacement });
90455
+ searchFrom = idx + query.length;
90456
+ count++;
90457
+ }
90458
+ return { ranges, count };
90459
+ }
90460
+ function applyRangeReplacements(runs, ranges) {
90461
+ if (ranges.length === 0) return;
90462
+ const bounds = [];
90463
+ let offset = 0;
90464
+ for (const run of runs) {
90465
+ bounds.push({ run, start: offset, end: offset + run.text.length });
90466
+ offset += run.text.length;
90467
+ }
90468
+ const joined = runs.map((r) => r.text).join("");
90469
+ const sorted = [...ranges].sort((a, b) => a.start - b.start);
90470
+ for (const { run, start: runStart, end: runEnd } of bounds) {
90471
+ let next = "";
90472
+ let cursor = runStart;
90473
+ for (const range2 of sorted) {
90474
+ if (range2.end <= runStart || range2.start >= runEnd) continue;
90475
+ const keepUntil = Math.min(range2.start, runEnd);
90476
+ const from = Math.max(cursor, runStart);
90477
+ if (keepUntil > from) next += joined.slice(from, keepUntil);
90478
+ if (runStart <= range2.start && range2.start < runEnd) next += range2.replacement;
90479
+ cursor = Math.max(cursor, range2.end);
90480
+ }
90481
+ const tail = Math.max(cursor, runStart);
90482
+ if (runEnd > tail) next += joined.slice(tail, runEnd);
90483
+ if (next !== run.text) setRunText(run, next);
90484
+ }
90485
+ }
90486
+ function replaceInParagraph(el, query, replacement, matchType, maxReplacements, replacementsMade) {
90487
+ if (!extractParagraphText(el)) return replacementsMade;
90488
+ const runs = getRunsFromParagraph(el);
90489
+ if (runs.length === 0) return replacementsMade;
90490
+ const text = runs.length === 1 ? runs[0].text : runs.map((r) => r.text).join("");
90491
+ const { ranges, count } = computeReplacementRanges(text, query, replacement, matchType, maxReplacements, replacementsMade);
90492
+ applyRangeReplacements(runs, ranges);
90493
+ return count;
90494
+ }
90495
+ function replaceText(bodyElements, query, replacement, matchType = "contains", maxReplacements = Infinity) {
90496
+ if (matchType === "regex") {
90497
+ try {
90498
+ new RegExp(query);
90499
+ } catch {
90500
+ throw new Error(`Invalid regex pattern: ${query}`);
90501
+ }
90502
+ }
90503
+ let replacementsMade = 0;
90504
+ for (let i2 = 0; i2 < bodyElements.length && replacementsMade < maxReplacements; i2++) {
90505
+ const el = bodyElements[i2];
90506
+ const type = getBodyElementType(el);
90507
+ if (type === "paragraph") {
90508
+ replacementsMade = replaceInParagraph(el, query, replacement, matchType, maxReplacements, replacementsMade);
90509
+ } else if (type === "table") {
90510
+ for (const child of getChildren(el)) {
90511
+ if (getTagName(child) !== "w:tr") continue;
90512
+ for (const cell of getChildren(child)) {
90513
+ if (getTagName(cell) !== "w:tc") continue;
90514
+ for (const p of getChildren(cell)) {
90515
+ if (getTagName(p) === "w:p" && replacementsMade < maxReplacements) {
90516
+ replacementsMade = replaceInParagraph(p, query, replacement, matchType, maxReplacements, replacementsMade);
90517
+ }
90518
+ }
90519
+ }
90520
+ }
90521
+ }
90522
+ }
90523
+ return replacementsMade;
90524
+ }
90525
+
90255
90526
  // ../docx/src/parse/parser.ts
90256
90527
  var import_jszip2 = __toESM(require_lib4(), 1);
90257
90528
  async function parseDocx(buffer) {
@@ -90778,18 +91049,102 @@ async function docxReplaceText(filePath, rest) {
90778
91049
  fail2("Usage: lotics docx replace-text <file> '<search>' '<replace>'");
90779
91050
  }
90780
91051
  const doc = await loadFile2(filePath);
90781
- const newChildren = doc.body.children.map((b) => b.kind === "paragraph" ? replaceInParagraph(b, search, replace2) : b);
90782
- await writeDoc(filePath, replaceChildren(doc, newChildren));
91052
+ const { children, count } = replaceEverywhere(doc.body.children, search, replace2);
91053
+ if (count === 0) {
91054
+ fail2(`No occurrence of ${JSON.stringify(search)} in ${filePath} \u2014 nothing written.`);
91055
+ }
91056
+ await writeDoc(filePath, replaceChildren(doc, children));
91057
+ console.error(`Wrote ${filePath} (${count} replacement${count === 1 ? "" : "s"})`);
90783
91058
  }
90784
- function replaceInParagraph(p, search, replace2) {
90785
- const content = p.content.map((inline) => {
91059
+ function replaceEverywhere(blocks, search, replace2) {
91060
+ let count = 0;
91061
+ const children = blocks.map((b) => {
91062
+ if (b.kind === "paragraph") {
91063
+ const [paragraph, n] = replaceInParagraph2(b, search, replace2);
91064
+ count += n;
91065
+ return paragraph;
91066
+ }
91067
+ if (b.kind === "opaque_block") {
91068
+ const xml = structuredClone(b.xml);
91069
+ const n = replaceText([xml], search, replace2, "contains");
91070
+ if (n === 0) return b;
91071
+ count += n;
91072
+ return { kind: "opaque_block", xml };
91073
+ }
91074
+ return b;
91075
+ });
91076
+ return { children, count };
91077
+ }
91078
+ function replaceInParagraph2(p, search, replace2) {
91079
+ if (search === "") return [p, 0];
91080
+ const rewritten = /* @__PURE__ */ new Map();
91081
+ let count = 0;
91082
+ for (const segment of collectTextSegments(p)) {
91083
+ const joined = segment.map((s) => s.value).join("");
91084
+ const matches = [];
91085
+ for (let at2 = joined.indexOf(search); at2 !== -1; at2 = joined.indexOf(search, at2 + search.length)) {
91086
+ matches.push({ start: at2, end: at2 + search.length });
91087
+ }
91088
+ if (matches.length === 0) continue;
91089
+ count += matches.length;
91090
+ for (const slot of segment) {
91091
+ let out = "";
91092
+ let cursor = slot.start;
91093
+ for (const m of matches) {
91094
+ if (m.end <= slot.start || m.start >= slot.end) continue;
91095
+ const keepUntil = Math.max(cursor, Math.min(m.start, slot.end));
91096
+ if (keepUntil > cursor) out += joined.slice(cursor, keepUntil);
91097
+ if (m.start >= slot.start && m.start < slot.end) out += replace2;
91098
+ cursor = Math.max(cursor, Math.min(m.end, slot.end));
91099
+ }
91100
+ if (cursor < slot.end) out += joined.slice(cursor, slot.end);
91101
+ rewritten.set(`${slot.inlineIndex}:${slot.childIndex}`, out);
91102
+ }
91103
+ }
91104
+ if (count === 0) return [p, 0];
91105
+ const content = p.content.map((inline, inlineIndex) => {
90786
91106
  if (inline.kind !== "run") return inline;
90787
- const newChildren = inline.content.map(
90788
- (c) => c.kind === "text" ? { kind: "text", value: c.value.split(search).join(replace2), preserveSpace: c.preserveSpace } : c
90789
- );
90790
- return { kind: "run", properties: inline.properties, content: newChildren };
91107
+ const children = inline.content.map((c, childIndex) => {
91108
+ if (c.kind !== "text") return c;
91109
+ const next = rewritten.get(`${inlineIndex}:${childIndex}`);
91110
+ if (next === void 0 || next === c.value) return c;
91111
+ return { kind: "text", value: next, preserveSpace: next.startsWith(" ") || next.endsWith(" ") };
91112
+ });
91113
+ return { kind: "run", properties: inline.properties, content: children };
91114
+ });
91115
+ return [{ kind: "paragraph", properties: p.properties, content }, count];
91116
+ }
91117
+ function collectTextSegments(p) {
91118
+ const segments = [];
91119
+ let current = [];
91120
+ let offset = 0;
91121
+ const breakSegment = () => {
91122
+ if (current.length > 0) segments.push(current);
91123
+ current = [];
91124
+ offset = 0;
91125
+ };
91126
+ p.content.forEach((inline, inlineIndex) => {
91127
+ if (inline.kind !== "run") {
91128
+ breakSegment();
91129
+ return;
91130
+ }
91131
+ inline.content.forEach((child, childIndex) => {
91132
+ if (child.kind !== "text") {
91133
+ breakSegment();
91134
+ return;
91135
+ }
91136
+ current.push({
91137
+ inlineIndex,
91138
+ childIndex,
91139
+ value: child.value,
91140
+ start: offset,
91141
+ end: offset + child.value.length
91142
+ });
91143
+ offset += child.value.length;
91144
+ });
90791
91145
  });
90792
- return { kind: "paragraph", properties: p.properties, content };
91146
+ breakSegment();
91147
+ return segments.filter((s) => s.length > 0);
90793
91148
  }
90794
91149
  async function docxBatch(filePath, json2) {
90795
91150
  if (!filePath || !json2) fail2("Usage: lotics docx batch <file> '<json-array-of-ops>'");
@@ -90833,8 +91188,9 @@ function applyBatchOp(doc, op, index) {
90833
91188
  return replaceChildren(doc, doc.body.children.filter((_, i2) => i2 !== op.at));
90834
91189
  }
90835
91190
  case "replace-text": {
90836
- const next = doc.body.children.map((b) => b.kind === "paragraph" ? replaceInParagraph(b, op.search, op.replace) : b);
90837
- return replaceChildren(doc, next);
91191
+ const { children, count } = replaceEverywhere(doc.body.children, op.search, op.replace);
91192
+ if (count === 0) fail2(`[op ${index}] No occurrence of ${JSON.stringify(op.search)} \u2014 batch aborted.`);
91193
+ return replaceChildren(doc, children);
90838
91194
  }
90839
91195
  default: {
90840
91196
  const exhaustive = op;
@@ -90854,10 +91210,12 @@ Uses Lotics' own OOXML engine; round-trips faithfully with the Lotics editor and
90854
91210
  lotics docx insert-paragraph <file> '<text>' --at=<i> [--style=NAME]
90855
91211
  Insert a paragraph at index <i> (0-based)
90856
91212
  lotics docx delete-block <file> --at=<i> Remove the block at index <i>
90857
- lotics docx replace-text <file> '<search>' '<replace>' Replace text in all paragraph runs
91213
+ lotics docx replace-text <file> '<search>' '<replace>' Replace text in paragraphs AND table cells
91214
+ (errors if nothing matched \u2014 never a silent no-op)
90858
91215
  lotics docx batch <file> '<json-array-of-ops>' Apply many ops in one parse/serialize cycle
90859
91216
 
90860
- Edit ops mutate the file atomically (temp file + rename). Opaque blocks (tables, custom XML) are preserved verbatim.`);
91217
+ Edit ops mutate the file atomically (temp file + rename). Structure inside opaque blocks (tables, custom XML) is
91218
+ preserved verbatim; replace-text rewrites the text within them.`);
90861
91219
  }
90862
91220
  async function runDocxCommand(subcommand, toolArgs, restArgs) {
90863
91221
  switch (subcommand) {
@@ -91522,8 +91880,8 @@ Multiple workspaces in ${info.organization_name}. Run "lotics workspace select <
91522
91880
  console.error(` Saved to the "${info.organization_name}" profile in ~/.lotics`);
91523
91881
  }
91524
91882
  }
91525
- function requireClient(flags) {
91526
- const ctx = resolveContext(flags);
91883
+ function requireClient(flags, appWorkspaceId) {
91884
+ const ctx = resolveContext(flags, appWorkspaceId);
91527
91885
  if (!ctx) {
91528
91886
  console.error('Not authenticated. Run "lotics auth signup", "lotics auth api-key <key>", or set LOTICS_API_KEY.');
91529
91887
  process.exit(1);
@@ -91539,6 +91897,7 @@ var SOURCE_LABELS = {
91539
91897
  env_key: "LOTICS_API_KEY env",
91540
91898
  env_org: "LOTICS_ORG env",
91541
91899
  local_pointer: "local .lotics/config.json (pin)",
91900
+ app_manifest_profile: "app manifest workspace \u2192 its saved profile",
91542
91901
  global_profile: "global active profile"
91543
91902
  };
91544
91903
  function announceTarget(ctx, workspaceId) {
@@ -91552,12 +91911,15 @@ function announceTarget(ctx, workspaceId) {
91552
91911
  }
91553
91912
  console.error(`lotics \u2192 ${org} / ${workspaceId}`);
91554
91913
  }
91555
- function applyAppManifestWorkspace(ctx, command, subcommand, pathArg, flags) {
91556
- if (command !== "app") return;
91557
- if (flags.workspace || process.env.LOTICS_WORKSPACE) return;
91558
- if (subcommand === "create" || subcommand === "pull") return;
91914
+ function appManifestWorkspaceId(command, subcommand, pathArg, flags) {
91915
+ if (command !== "app") return void 0;
91916
+ if (flags.workspace || process.env.LOTICS_WORKSPACE) return void 0;
91917
+ if (subcommand === "create" || subcommand === "pull") return void 0;
91559
91918
  const dir = pathArg && !pathArg.startsWith("-") ? pathArg : process.cwd();
91560
- const workspaceId = readAppWorkspaceId(dir);
91919
+ return readAppWorkspaceId(dir) ?? void 0;
91920
+ }
91921
+ function applyAppManifestWorkspace(ctx, command, subcommand, pathArg, flags) {
91922
+ const workspaceId = appManifestWorkspaceId(command, subcommand, pathArg, flags);
91561
91923
  if (workspaceId) ctx.workspaceId = workspaceId;
91562
91924
  }
91563
91925
  async function resolveWorkspace(client, ctx) {
@@ -91757,7 +92119,7 @@ async function main() {
91757
92119
  }
91758
92120
  if (command === "app" && subcommand === "codegen") {
91759
92121
  const projectDir = toolArgs;
91760
- const ctx2 = resolveContext(flags);
92122
+ const ctx2 = resolveContext(flags, appManifestWorkspaceId(command, subcommand, projectDir, flags));
91761
92123
  if (!ctx2) {
91762
92124
  await appCodegen({ projectDir });
91763
92125
  return;
@@ -91874,7 +92236,11 @@ async function main() {
91874
92236
  console.error("Downloads every file on the given file field into the output dir.");
91875
92237
  process.exit(1);
91876
92238
  }
91877
- const { client, ctx } = requireClient(flags);
92239
+ const appPathArg = subcommand === "dev" ? toolArgs : void 0;
92240
+ const { client, ctx } = requireClient(
92241
+ flags,
92242
+ appManifestWorkspaceId(command, subcommand, appPathArg, flags)
92243
+ );
91878
92244
  if (command === "workspace") {
91879
92245
  if (subcommand === "doctor") {
91880
92246
  await resolveWorkspace(client, ctx);
@@ -91988,7 +92354,7 @@ Available workspaces:`);
91988
92354
  if (ctx.orgName) console.error(`Org: ${ctx.orgName}`);
91989
92355
  return;
91990
92356
  }
91991
- applyAppManifestWorkspace(ctx, command, subcommand, subcommand === "dev" ? toolArgs : void 0, flags);
92357
+ applyAppManifestWorkspace(ctx, command, subcommand, appPathArg, flags);
91992
92358
  await resolveWorkspace(client, ctx);
91993
92359
  if (command === "knowledge") {
91994
92360
  await runKnowledgeCommand(client, subcommand, toolArgs, flags);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.96.5",
3
+ "version": "0.97.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {