@lotics/cli 0.96.6 → 0.98.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
@@ -44165,9 +44165,7 @@ import os2 from "node:os";
44165
44165
  import path7 from "node:path";
44166
44166
  import readline from "node:readline";
44167
44167
 
44168
- // src/client.ts
44169
- import fs from "node:fs";
44170
- import path from "node:path";
44168
+ // ../shared/src/transport_error.ts
44171
44169
  function gatewayErrorMessage(status) {
44172
44170
  if (status === 524) {
44173
44171
  return "The request took too long to finish (gateway timeout). It may still be running \u2014 check back in a moment, or try again.";
@@ -44181,6 +44179,10 @@ function transportErrorMessage(status, parsed) {
44181
44179
  const jsonMessage = parsed && typeof parsed.message === "string" ? parsed.message : null;
44182
44180
  return parsed === null || status >= 500 || jsonMessage === null ? gatewayErrorMessage(status) : jsonMessage;
44183
44181
  }
44182
+
44183
+ // src/client.ts
44184
+ import fs from "node:fs";
44185
+ import path from "node:path";
44184
44186
  function findAvailableFilename(dir, filename, reserved) {
44185
44187
  const isTaken = (name) => {
44186
44188
  const full = path.join(dir, name);
@@ -45107,7 +45109,7 @@ function resolveProfileByNameOrId(profiles, nameOrId) {
45107
45109
  }
45108
45110
  return null;
45109
45111
  }
45110
- function resolveContext(flags) {
45112
+ function resolveContext(flags, appWorkspaceId) {
45111
45113
  const envKey = process.env.LOTICS_API_KEY;
45112
45114
  const envOrg = process.env.LOTICS_ORG;
45113
45115
  const envWorkspace = process.env.LOTICS_WORKSPACE;
@@ -45157,6 +45159,19 @@ function resolveContext(flags) {
45157
45159
  `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
45160
  );
45159
45161
  }
45162
+ if (appWorkspaceId && !wsOverride) {
45163
+ const matches = Object.entries(profiles).filter(([, p]) => p.workspace_id === appWorkspaceId);
45164
+ if (matches.length === 1) {
45165
+ const [orgId, profile] = matches[0];
45166
+ return {
45167
+ apiKey: profile.api_key,
45168
+ orgId,
45169
+ orgName: profile.org_name,
45170
+ workspaceId: appWorkspaceId,
45171
+ source: "app_manifest_profile"
45172
+ };
45173
+ }
45174
+ }
45160
45175
  if (global2?.active_org) {
45161
45176
  const profile = profiles[global2.active_org];
45162
45177
  if (!profile) {
@@ -64402,7 +64417,8 @@ var aggregateOperationSchema = external_exports.enum([
64402
64417
  "checked",
64403
64418
  "unchecked",
64404
64419
  "percent_checked",
64405
- "percent_unchecked"
64420
+ "percent_unchecked",
64421
+ "string_agg"
64406
64422
  ]);
64407
64423
  var OPERATION_FIELD_TYPES = {
64408
64424
  empty: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup"],
@@ -64517,12 +64533,21 @@ var queryProjectionColumnSchema = zod_default.union([
64517
64533
  zod_default.string().describe("Passthrough shorthand: a source field key. Output = the key, type = the field's type."),
64518
64534
  queryProjectionColumnObjectSchema
64519
64535
  ]);
64536
+ var STRING_AGG_MAX_VALUES = 100;
64537
+ var STRING_AGG_DEFAULT_MAX_VALUES = 20;
64520
64538
  var queryAggregateColumnSchema = zod_default.object({
64521
64539
  output: zod_default.string().describe("Output column name"),
64522
64540
  type: queryColumnTypeSchema.describe("Output column type"),
64523
64541
  operation: aggregateOperationSchema,
64524
64542
  input_column: zod_default.string().optional().describe(
64525
64543
  "Input column to aggregate. Required for non-count operations; ignored for count."
64544
+ ),
64545
+ separator: zod_default.string().max(8).optional().describe('string_agg only. Joins the values. Defaults to ", ".'),
64546
+ distinct: zod_default.boolean().optional().describe(
64547
+ "string_agg only. Collapses repeats \u2014 a group of containers sized 40HC/40HC/20DC aggregates to '20DC, 40HC'. Defaults to true, which is almost always what a summary column wants."
64548
+ ),
64549
+ max_values: zod_default.number().int().min(1).max(STRING_AGG_MAX_VALUES).optional().describe(
64550
+ `string_agg only. Caps how many values are emitted so an unbounded child set cannot produce a giant cell. Defaults to ${STRING_AGG_DEFAULT_MAX_VALUES}. Pair with a \`unique\` aggregate to render an accurate '+N more'.`
64526
64551
  )
64527
64552
  });
64528
64553
  var rankingWindowFunctionSchema = (fn) => zod_default.object({ output: zod_default.string().describe("Output column name"), fn: zod_default.literal(fn) }).strict();
@@ -64879,6 +64904,12 @@ var appAgentDeclarationSchema = zod_default.object({
64879
64904
  knowledge_doc_ids: zod_default.array(zod_default.string().min(1)).optional().describe(
64880
64905
  "Knowledge docs the agent may read, validated at declare time against the app owner's `use` access. Small docs are inlined into the agent's system prompt each run; a doc too large to inline requires the code tools (code_exec) in tool_names and is read by staging it into a code run. Omit for an agent that needs no knowledge."
64881
64906
  ),
64907
+ query_aliases: zod_default.array(zod_default.string().min(1)).optional().describe(
64908
+ "Named queries from this app's manifest the agent may run via `run_app_query`, validated at declare time against the app's own queries. This is the agent's ENTIRE read surface over workspace data \u2014 a template fixes the tables, filters, and projection, so it bounds rows and columns, not just tables. Omit for an agent that reads no records."
64909
+ ),
64910
+ workflow_aliases: zod_default.array(zod_default.string().min(1)).optional().describe(
64911
+ "Workflows from this app's manifest the agent may invoke via `run_app_workflow`, validated at declare time against the app's own workflows. This is the agent's ENTIRE write surface \u2014 the same declared mutation path the app's UI uses, so table hooks and side-effect harvesting apply. Omit for a read-only agent."
64912
+ ),
64882
64913
  model_id: zod_default.string().min(1).optional().describe(
64883
64914
  "Chat model id the agent runs on, validated against ACCEPTED_CHAT_MODEL_IDS. Omit to follow the platform default chat model, resolved at run time \u2014 the preferred choice: the agent tracks model generations with no per-app rewrite. Pin only a deliberate, tested choice."
64884
64915
  ),
@@ -79139,8 +79170,9 @@ function parseCols(worksheet, defaultColWidth) {
79139
79170
  const max3 = parseInt(col["@_max"] ?? "1", 10);
79140
79171
  const width = col["@_width"] ? parseFloat(col["@_width"]) : defaultColWidth;
79141
79172
  const hidden = col["@_hidden"] === "1" || col["@_hidden"] === "true";
79173
+ const customWidth = col["@_customWidth"] === "1" || col["@_customWidth"] === "true";
79142
79174
  const outlineLevel = parseInt(col["@_outlineLevel"] ?? "0", 10);
79143
- columns.push({ min: min2, max: max3, width, hidden, outlineLevel });
79175
+ columns.push({ min: min2, max: max3, width, hidden, customWidth, outlineLevel });
79144
79176
  for (let c = min2; c <= max3; c++) {
79145
79177
  if (hidden) colHidden.add(c);
79146
79178
  if (outlineLevel > 0) colOutlineLevels.set(c, outlineLevel);
@@ -79150,9 +79182,11 @@ function parseCols(worksheet, defaultColWidth) {
79150
79182
  }
79151
79183
  function buildColumns(colDefs, colHidden, maxContentWidth, colCount, _defaultColWidth) {
79152
79184
  const explicitWidth = /* @__PURE__ */ new Map();
79185
+ const pinned = /* @__PURE__ */ new Set();
79153
79186
  for (const def of colDefs) {
79154
79187
  for (let c = def.min; c <= def.max; c++) {
79155
79188
  explicitWidth.set(c, def.width);
79189
+ if (def.customWidth) pinned.add(c);
79156
79190
  }
79157
79191
  }
79158
79192
  const columns = [];
@@ -79166,7 +79200,7 @@ function buildColumns(colDefs, colHidden, maxContentWidth, colCount, _defaultCol
79166
79200
  } else {
79167
79201
  width = Math.max(8, Math.min(contentChars + 2, 40));
79168
79202
  }
79169
- columns.push({ index: c, width, hidden });
79203
+ columns.push({ index: c, width, hidden, ...pinned.has(c) ? { customWidth: true } : {} });
79170
79204
  }
79171
79205
  return columns;
79172
79206
  }
@@ -79210,6 +79244,7 @@ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultR
79210
79244
  rowCount++;
79211
79245
  if (rowCount > maxRows) continue;
79212
79246
  const height = rowEl["@_ht"] ? parseFloat(rowEl["@_ht"]) : defaultRowHeight;
79247
+ const customHeight = rowEl["@_customHeight"] === "1" || rowEl["@_customHeight"] === "true";
79213
79248
  const cellArr = rowEl["c"];
79214
79249
  const cells = [];
79215
79250
  if (cellArr) {
@@ -79231,7 +79266,8 @@ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultR
79231
79266
  index: rowIndex,
79232
79267
  height,
79233
79268
  cells,
79234
- hidden: rowHidden
79269
+ hidden: rowHidden,
79270
+ ...customHeight ? { customHeight: true } : {}
79235
79271
  });
79236
79272
  }
79237
79273
  return {
@@ -83767,7 +83803,7 @@ function loadSheetFromParsed(ps, styles) {
83767
83803
  sheet.freeze = { row: ps.freezePane.frozenRows, col: ps.freezePane.frozenCols };
83768
83804
  }
83769
83805
  for (const col of ps.columns) {
83770
- if (col.width !== ps.defaultColWidth) {
83806
+ if (col.customWidth || col.width !== ps.defaultColWidth) {
83771
83807
  sheet.colWidths.set(col.index, col.width);
83772
83808
  }
83773
83809
  if (col.hidden) {
@@ -83780,7 +83816,7 @@ function loadSheetFromParsed(ps, styles) {
83780
83816
  return `${startRef}:${endRef}`;
83781
83817
  });
83782
83818
  for (const row of ps.rows) {
83783
- if (row.height !== ps.defaultRowHeight) {
83819
+ if (row.customHeight || row.height !== ps.defaultRowHeight) {
83784
83820
  sheet.rowHeights.set(row.index, row.height);
83785
83821
  }
83786
83822
  if (row.hidden) {
@@ -84668,6 +84704,103 @@ function clampInertCells(sheet) {
84668
84704
  for (const ref of pruned.keys()) sheet.cells.delete(ref);
84669
84705
  return pruned;
84670
84706
  }
84707
+ function axisStores(sheet, axis) {
84708
+ return axis === "row" ? {
84709
+ sizes: sheet.rowHeights,
84710
+ outline: sheet.rowOutlineLevels,
84711
+ hidden: sheet.hiddenRows,
84712
+ breaks: sheet.rowPageBreaks,
84713
+ setBreaks: (next) => {
84714
+ sheet.rowPageBreaks = next;
84715
+ }
84716
+ } : {
84717
+ sizes: sheet.colWidths,
84718
+ outline: sheet.colOutlineLevels,
84719
+ hidden: sheet.hiddenCols,
84720
+ breaks: sheet.colPageBreaks,
84721
+ setBreaks: (next) => {
84722
+ sheet.colPageBreaks = next;
84723
+ }
84724
+ };
84725
+ }
84726
+ function shiftedIndex(i2, at2, delta) {
84727
+ if (i2 < at2) return i2;
84728
+ if (delta < 0 && i2 < at2 - delta) return null;
84729
+ return i2 + delta;
84730
+ }
84731
+ function shiftKeys(map3, at2, delta) {
84732
+ const affected = [...map3.entries()].filter(([i2]) => i2 >= at2);
84733
+ for (const [i2] of affected) map3.delete(i2);
84734
+ for (const [i2, value] of affected) {
84735
+ const next = shiftedIndex(i2, at2, delta);
84736
+ if (next !== null) map3.set(next, value);
84737
+ }
84738
+ }
84739
+ function shiftMembers(set2, at2, delta) {
84740
+ const affected = [...set2].filter((i2) => i2 >= at2);
84741
+ for (const i2 of affected) set2.delete(i2);
84742
+ for (const i2 of affected) {
84743
+ const next = shiftedIndex(i2, at2, delta);
84744
+ if (next !== null) set2.add(next);
84745
+ }
84746
+ }
84747
+ function shiftBreaks(breaks, at2, delta) {
84748
+ const moved = breaks.map((i2) => shiftedIndex(i2, at2, delta)).filter((i2) => i2 !== null);
84749
+ return [...new Set(moved)].sort((a, b) => a - b);
84750
+ }
84751
+ function remapHyperlinks(sheet, landing) {
84752
+ const next = /* @__PURE__ */ new Map();
84753
+ for (const [ref, url2] of sheet.hyperlinks) {
84754
+ const rc = refToRowColSafe(ref);
84755
+ if (!rc) {
84756
+ next.set(ref, url2);
84757
+ continue;
84758
+ }
84759
+ const to = landing(rc.row, rc.col);
84760
+ if (to) next.set(rowColToRef(to.row, to.col), url2);
84761
+ }
84762
+ sheet.hyperlinks = next;
84763
+ }
84764
+ function axisLanding(axis, at2, delta) {
84765
+ return (row, col) => {
84766
+ const next = shiftedIndex(axis === "row" ? row : col, at2, delta);
84767
+ if (next === null) return null;
84768
+ return axis === "row" ? { row: next, col } : { row, col: next };
84769
+ };
84770
+ }
84771
+ function shiftAxisMetadata(sheet, axis, at2, delta) {
84772
+ const stores = axisStores(sheet, axis);
84773
+ shiftKeys(stores.sizes, at2, delta);
84774
+ shiftKeys(stores.outline, at2, delta);
84775
+ shiftMembers(stores.hidden, at2, delta);
84776
+ stores.setBreaks(shiftBreaks(stores.breaks, at2, delta));
84777
+ remapHyperlinks(sheet, axisLanding(axis, at2, delta));
84778
+ }
84779
+ var EMPTY_BAND = { sizes: [], outline: [], hidden: [], breaks: [], hyperlinks: [] };
84780
+ function snapshotAxisBand(sheet, axis, at2, count) {
84781
+ const stores = axisStores(sheet, axis);
84782
+ const inBand = (i2) => i2 >= at2 && i2 < at2 + count;
84783
+ const links = [];
84784
+ for (const [ref, url2] of sheet.hyperlinks) {
84785
+ const rc = refToRowColSafe(ref);
84786
+ if (rc && inBand(axis === "row" ? rc.row : rc.col)) links.push([ref, url2]);
84787
+ }
84788
+ return {
84789
+ sizes: [...stores.sizes.entries()].filter(([i2]) => inBand(i2)),
84790
+ outline: [...stores.outline.entries()].filter(([i2]) => inBand(i2)),
84791
+ hidden: [...stores.hidden].filter(inBand),
84792
+ breaks: stores.breaks.filter(inBand),
84793
+ hyperlinks: links
84794
+ };
84795
+ }
84796
+ function restoreAxisBand(sheet, axis, band) {
84797
+ const stores = axisStores(sheet, axis);
84798
+ for (const [i2, value] of band.sizes) stores.sizes.set(i2, value);
84799
+ for (const [i2, value] of band.outline) stores.outline.set(i2, value);
84800
+ for (const i2 of band.hidden) stores.hidden.add(i2);
84801
+ stores.setBreaks([.../* @__PURE__ */ new Set([...stores.breaks, ...band.breaks])].sort((a, b) => a - b));
84802
+ for (const [ref, url2] of band.hyperlinks) sheet.hyperlinks.set(ref, url2);
84803
+ }
84671
84804
  function insertRowsCommand(workbook, sheetIndex, at2, count) {
84672
84805
  const sheet = workbook.sheets[sheetIndex];
84673
84806
  let mergeSnapshot = [];
@@ -84688,11 +84821,7 @@ function insertRowsCommand(workbook, sheetIndex, at2, count) {
84688
84821
  sheet.cells.delete(ref);
84689
84822
  sheet.cells.set(rowColToRef(row + count, col), cell);
84690
84823
  }
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
- }
84824
+ shiftAxisMetadata(sheet, "row", at2, count);
84696
84825
  adjustAllFormulasForRowShift(sheet, at2, count);
84697
84826
  adjustMergedCellsForRowInsert(sheet, at2, count);
84698
84827
  workbook.emit({ type: "rows_inserted", sheet: sheetIndex, at: at2, count });
@@ -84714,12 +84843,7 @@ function insertRowsCommand(workbook, sheetIndex, at2, count) {
84714
84843
  sheet.cells.delete(ref);
84715
84844
  sheet.cells.set(rowColToRef(row - count, col), cell);
84716
84845
  }
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);
84846
+ shiftAxisMetadata(sheet, "row", at2, -count);
84723
84847
  for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
84724
84848
  workbook.emit({ type: "rows_deleted", sheet: sheetIndex, at: at2, count });
84725
84849
  }
@@ -84736,11 +84860,13 @@ function deleteRowsCommand(workbook, sheetIndex, at2, count) {
84736
84860
  }
84737
84861
  let mergeSnapshot = [];
84738
84862
  let inertSnapshot = /* @__PURE__ */ new Map();
84863
+ let bandSnapshot = EMPTY_BAND;
84739
84864
  return {
84740
84865
  description: `Delete ${count} row${count > 1 ? "s" : ""} at ${at2}`,
84741
84866
  execute() {
84742
84867
  mergeSnapshot = [...sheet.mergedCells];
84743
84868
  inertSnapshot = clampInertCells(sheet);
84869
+ bandSnapshot = snapshotAxisBand(sheet, "row", at2, count);
84744
84870
  for (const ref of snapshot.keys()) sheet.cells.delete(ref);
84745
84871
  const toMove = [];
84746
84872
  for (const [ref] of sheet.cells) {
@@ -84753,12 +84879,15 @@ function deleteRowsCommand(workbook, sheetIndex, at2, count) {
84753
84879
  sheet.cells.delete(ref);
84754
84880
  sheet.cells.set(rowColToRef(row - count, col), cell);
84755
84881
  }
84882
+ shiftAxisMetadata(sheet, "row", at2, -count);
84756
84883
  adjustAllFormulasForRowShift(sheet, at2, -count);
84757
84884
  adjustMergedCellsForRowDelete(sheet, at2, count);
84758
84885
  workbook.emit({ type: "rows_deleted", sheet: sheetIndex, at: at2, count });
84759
84886
  },
84760
84887
  undo() {
84761
84888
  sheet.mergedCells = [...mergeSnapshot];
84889
+ shiftAxisMetadata(sheet, "row", at2, count);
84890
+ restoreAxisBand(sheet, "row", bandSnapshot);
84762
84891
  const toMove = [];
84763
84892
  for (const [ref] of sheet.cells) {
84764
84893
  const rc = refToRowColSafe(ref);
@@ -84798,11 +84927,7 @@ function insertColsCommand(workbook, sheetIndex, at2, count) {
84798
84927
  sheet.cells.delete(ref);
84799
84928
  sheet.cells.set(rowColToRef(row, col + count), cell);
84800
84929
  }
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
- }
84930
+ shiftAxisMetadata(sheet, "col", at2, count);
84806
84931
  adjustAllFormulasForColShift(sheet, at2, count);
84807
84932
  adjustMergedCellsForColInsert(sheet, at2, count);
84808
84933
  workbook.emit({ type: "cols_inserted", sheet: sheetIndex, at: at2, count });
@@ -84824,12 +84949,7 @@ function insertColsCommand(workbook, sheetIndex, at2, count) {
84824
84949
  sheet.cells.delete(ref);
84825
84950
  sheet.cells.set(rowColToRef(row, col - count), cell);
84826
84951
  }
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);
84952
+ shiftAxisMetadata(sheet, "col", at2, -count);
84833
84953
  for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
84834
84954
  workbook.emit({ type: "cols_deleted", sheet: sheetIndex, at: at2, count });
84835
84955
  }
@@ -84846,11 +84966,13 @@ function deleteColsCommand(workbook, sheetIndex, at2, count) {
84846
84966
  }
84847
84967
  let mergeSnapshot = [];
84848
84968
  let inertSnapshot = /* @__PURE__ */ new Map();
84969
+ let bandSnapshot = EMPTY_BAND;
84849
84970
  return {
84850
84971
  description: `Delete ${count} column${count > 1 ? "s" : ""} at ${at2}`,
84851
84972
  execute() {
84852
84973
  mergeSnapshot = [...sheet.mergedCells];
84853
84974
  inertSnapshot = clampInertCells(sheet);
84975
+ bandSnapshot = snapshotAxisBand(sheet, "col", at2, count);
84854
84976
  for (const ref of snapshot.keys()) sheet.cells.delete(ref);
84855
84977
  const toMove = [];
84856
84978
  for (const [ref] of sheet.cells) {
@@ -84863,12 +84985,15 @@ function deleteColsCommand(workbook, sheetIndex, at2, count) {
84863
84985
  sheet.cells.delete(ref);
84864
84986
  sheet.cells.set(rowColToRef(row, col - count), cell);
84865
84987
  }
84988
+ shiftAxisMetadata(sheet, "col", at2, -count);
84866
84989
  adjustAllFormulasForColShift(sheet, at2, -count);
84867
84990
  adjustMergedCellsForColDelete(sheet, at2, count);
84868
84991
  workbook.emit({ type: "cols_deleted", sheet: sheetIndex, at: at2, count });
84869
84992
  },
84870
84993
  undo() {
84871
84994
  sheet.mergedCells = [...mergeSnapshot];
84995
+ shiftAxisMetadata(sheet, "col", at2, count);
84996
+ restoreAxisBand(sheet, "col", bandSnapshot);
84872
84997
  const toMove = [];
84873
84998
  for (const [ref] of sheet.cells) {
84874
84999
  const rc = refToRowColSafe(ref);
@@ -91773,8 +91898,8 @@ Multiple workspaces in ${info.organization_name}. Run "lotics workspace select <
91773
91898
  console.error(` Saved to the "${info.organization_name}" profile in ~/.lotics`);
91774
91899
  }
91775
91900
  }
91776
- function requireClient(flags) {
91777
- const ctx = resolveContext(flags);
91901
+ function requireClient(flags, appWorkspaceId) {
91902
+ const ctx = resolveContext(flags, appWorkspaceId);
91778
91903
  if (!ctx) {
91779
91904
  console.error('Not authenticated. Run "lotics auth signup", "lotics auth api-key <key>", or set LOTICS_API_KEY.');
91780
91905
  process.exit(1);
@@ -91790,6 +91915,7 @@ var SOURCE_LABELS = {
91790
91915
  env_key: "LOTICS_API_KEY env",
91791
91916
  env_org: "LOTICS_ORG env",
91792
91917
  local_pointer: "local .lotics/config.json (pin)",
91918
+ app_manifest_profile: "app manifest workspace \u2192 its saved profile",
91793
91919
  global_profile: "global active profile"
91794
91920
  };
91795
91921
  function announceTarget(ctx, workspaceId) {
@@ -91803,12 +91929,15 @@ function announceTarget(ctx, workspaceId) {
91803
91929
  }
91804
91930
  console.error(`lotics \u2192 ${org} / ${workspaceId}`);
91805
91931
  }
91806
- function applyAppManifestWorkspace(ctx, command, subcommand, pathArg, flags) {
91807
- if (command !== "app") return;
91808
- if (flags.workspace || process.env.LOTICS_WORKSPACE) return;
91809
- if (subcommand === "create" || subcommand === "pull") return;
91932
+ function appManifestWorkspaceId(command, subcommand, pathArg, flags) {
91933
+ if (command !== "app") return void 0;
91934
+ if (flags.workspace || process.env.LOTICS_WORKSPACE) return void 0;
91935
+ if (subcommand === "create" || subcommand === "pull") return void 0;
91810
91936
  const dir = pathArg && !pathArg.startsWith("-") ? pathArg : process.cwd();
91811
- const workspaceId = readAppWorkspaceId(dir);
91937
+ return readAppWorkspaceId(dir) ?? void 0;
91938
+ }
91939
+ function applyAppManifestWorkspace(ctx, command, subcommand, pathArg, flags) {
91940
+ const workspaceId = appManifestWorkspaceId(command, subcommand, pathArg, flags);
91812
91941
  if (workspaceId) ctx.workspaceId = workspaceId;
91813
91942
  }
91814
91943
  async function resolveWorkspace(client, ctx) {
@@ -92008,7 +92137,7 @@ async function main() {
92008
92137
  }
92009
92138
  if (command === "app" && subcommand === "codegen") {
92010
92139
  const projectDir = toolArgs;
92011
- const ctx2 = resolveContext(flags);
92140
+ const ctx2 = resolveContext(flags, appManifestWorkspaceId(command, subcommand, projectDir, flags));
92012
92141
  if (!ctx2) {
92013
92142
  await appCodegen({ projectDir });
92014
92143
  return;
@@ -92125,7 +92254,11 @@ async function main() {
92125
92254
  console.error("Downloads every file on the given file field into the output dir.");
92126
92255
  process.exit(1);
92127
92256
  }
92128
- const { client, ctx } = requireClient(flags);
92257
+ const appPathArg = subcommand === "dev" ? toolArgs : void 0;
92258
+ const { client, ctx } = requireClient(
92259
+ flags,
92260
+ appManifestWorkspaceId(command, subcommand, appPathArg, flags)
92261
+ );
92129
92262
  if (command === "workspace") {
92130
92263
  if (subcommand === "doctor") {
92131
92264
  await resolveWorkspace(client, ctx);
@@ -92239,7 +92372,7 @@ Available workspaces:`);
92239
92372
  if (ctx.orgName) console.error(`Org: ${ctx.orgName}`);
92240
92373
  return;
92241
92374
  }
92242
- applyAppManifestWorkspace(ctx, command, subcommand, subcommand === "dev" ? toolArgs : void 0, flags);
92375
+ applyAppManifestWorkspace(ctx, command, subcommand, appPathArg, flags);
92243
92376
  await resolveWorkspace(client, ctx);
92244
92377
  if (command === "knowledge") {
92245
92378
  await runKnowledgeCommand(client, subcommand, toolArgs, flags);
@@ -1,12 +1,4 @@
1
1
  import type { ContractConfigEntry, KnowledgeUpgradeEntry, ModifiedArtifact, PackageContentBinding, UpgradeResolutions } from "./package_content_types";
2
- /**
3
- * The error message for a non-ok response. A genuine JSON error (a 4xx carrying
4
- * a `message`) surfaces verbatim; a non-JSON body (a gateway HTML page), any
5
- * 5xx, or a JSON body without a `message` falls back to a body-free,
6
- * status-derived message. `parsed` is the JSON.parse of the body, or `null`.
7
- * In parity (by value, no shared dep) with `packages/app-sdk/src/rpc.ts`.
8
- */
9
- export declare function transportErrorMessage(status: number, parsed: unknown): string;
10
2
  /** One sort key forwarded to the app query RPC (wire shape of a `TableRecordSort` entry). */
11
3
  export interface AppQuerySortKey {
12
4
  field_key: string;
@@ -89,19 +81,20 @@ export interface ToolInfo {
89
81
  }
90
82
  /**
91
83
  * A single knowledge doc with its HYDRATED body — the shape of
92
- * `GET /v1/knowledge_docs/{id}`. `content` is resolved server-side from the
93
- * doc's content file (or the parked column for a legacy row), so this is the
94
- * one content-read path a non-sandbox client has. `content_file_id` is the
95
- * concurrency token the REST PATCH echoes; the `update_knowledge` TOOL CASes
96
- * internally, so a CLI caller never needs to pass it.
84
+ * `GET /v1/knowledge_docs/{id}`, and the one content-read path a non-sandbox
85
+ * client has. `content_sha` is the concurrency token the REST PATCH echoes back
86
+ * as `expected_content_sha`; the `update_knowledge` TOOL CASes internally, so a
87
+ * CLI caller never needs to pass it.
97
88
  */
98
89
  export interface KnowledgeDocDetail {
99
90
  id: string;
100
91
  workspace_id: string;
101
92
  name: string;
102
93
  description: string;
94
+ /** Folder address; "" is root. */
95
+ path: string;
103
96
  content: string;
104
- content_file_id: string | null;
97
+ content_sha: string | null;
105
98
  files: unknown[];
106
99
  created_at: string;
107
100
  updated_at: string;
@@ -1,36 +1,6 @@
1
+ import { transportErrorMessage } from "@lotics/shared/transport_error";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
- /**
4
- * A user-facing message for a transport/gateway failure — derived from the HTTP
5
- * status, never from the response body. A 524 (edge timeout on a long run), any
6
- * 5xx, or a non-JSON body (an HTML error page) must NOT surface its raw body. In
7
- * parity (by value, no shared dep) with the published transport in
8
- * `packages/app-sdk/src/rpc.ts`.
9
- */
10
- function gatewayErrorMessage(status) {
11
- if (status === 524) {
12
- return "The request took too long to finish (gateway timeout). It may still be running — check back in a moment, or try again.";
13
- }
14
- if (status >= 500) {
15
- return "The service is temporarily unavailable. Please try again shortly.";
16
- }
17
- return "The service returned an unexpected response. Please try again.";
18
- }
19
- /**
20
- * The error message for a non-ok response. A genuine JSON error (a 4xx carrying
21
- * a `message`) surfaces verbatim; a non-JSON body (a gateway HTML page), any
22
- * 5xx, or a JSON body without a `message` falls back to a body-free,
23
- * status-derived message. `parsed` is the JSON.parse of the body, or `null`.
24
- * In parity (by value, no shared dep) with `packages/app-sdk/src/rpc.ts`.
25
- */
26
- export function transportErrorMessage(status, parsed) {
27
- const jsonMessage = parsed && typeof parsed.message === "string"
28
- ? parsed.message
29
- : null;
30
- return parsed === null || status >= 500 || jsonMessage === null
31
- ? gatewayErrorMessage(status)
32
- : jsonMessage;
33
- }
34
4
  function findAvailableFilename(dir, filename, reserved) {
35
5
  // `reserved` tracks absolute paths claimed by in-flight downloads in the same
36
6
  // batch — required for parallel callers because the file may not be on disk
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.96.6",
3
+ "version": "0.98.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {