@lotics/cli 0.93.0 → 0.94.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/dist/src/cli.js CHANGED
@@ -29586,6 +29586,7 @@ var require_lib3 = __commonJS({
29586
29586
  import dns from "node:dns";
29587
29587
  import net2 from "node:net";
29588
29588
  import fs9 from "node:fs";
29589
+ import os2 from "node:os";
29589
29590
  import path7 from "node:path";
29590
29591
  import readline from "node:readline";
29591
29592
 
@@ -30198,11 +30199,18 @@ var LoticsClient = class {
30198
30199
  * `async function __workflow(): …` wrapper the server compiles inside, so the
30199
30200
  * local typecheck mirrors the set-time verdict. Mirrors
30200
30201
  * POST /v1/apps/{app_id}/workflows/{alias}/dts.
30202
+ *
30203
+ * `declaration` (the manifest's `{ inputs?, outputs? }`) is posted as the body
30204
+ * `{ declaration }` ONLY when the alias isn't `set` on the server yet — the
30205
+ * server then synthesizes the dts from the declared schemas instead of 400ing
30206
+ * "no workflow alias". A registered alias needs no declaration (the server's
30207
+ * own bound contract wins), so the field is omitted in that case.
30201
30208
  */
30202
- async getAppWorkflowDts(app_id, alias) {
30209
+ async getAppWorkflowDts(app_id, alias, declaration) {
30203
30210
  return this.request(
30204
30211
  "POST",
30205
- `/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/dts`
30212
+ `/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/dts`,
30213
+ declaration ? { declaration } : void 0
30206
30214
  );
30207
30215
  }
30208
30216
  /**
@@ -30224,6 +30232,25 @@ var LoticsClient = class {
30224
30232
  if (!res.ok) await this.throwResponseError(res);
30225
30233
  return res;
30226
30234
  }
30235
+ /**
30236
+ * Continue a PARKED (`awaiting_input`) agent run with the user's answer to its
30237
+ * pending `ask_user_choice` — returns the RAW streamed continuation `Response`,
30238
+ * exactly like `appAgentRunStream`. Mirrors
30239
+ * POST /v1/apps/{app_id}/agent-runs/{run_id}/continue.
30240
+ */
30241
+ async appAgentRunContinueStream(app_id, run_id, body, signal) {
30242
+ const res = await fetch(
30243
+ `${this.baseUrl}/v1/apps/${encodeURIComponent(app_id)}/agent-runs/${encodeURIComponent(run_id)}/continue`,
30244
+ {
30245
+ method: "POST",
30246
+ headers: { ...this.buildHeaders(), "Content-Type": "application/json" },
30247
+ body: JSON.stringify(body),
30248
+ signal
30249
+ }
30250
+ );
30251
+ if (!res.ok) await this.throwResponseError(res);
30252
+ return res;
30253
+ }
30227
30254
  /**
30228
30255
  * A session's app-agent run history, oldest-first (the run just started is the
30229
30256
  * last, and its exact id is on the stream response's `x-app-agent-run-id`
@@ -31801,14 +31828,14 @@ function buildWrapperPage(args) {
31801
31828
  // protocol expects (stream-chunk* -> stream-end). Mirrors the production
31802
31829
  // host in frontend/features/app_ui/app_iframe_host.tsx.
31803
31830
  const streamingRuns = {};
31804
- async function handleAgentRun(id, payload) {
31831
+ async function handleAgentRun(id, op, payload) {
31805
31832
  const controller = new AbortController();
31806
31833
  streamingRuns[id] = controller;
31807
31834
  try {
31808
31835
  const res = await fetch("/_agent_run", {
31809
31836
  method: "POST",
31810
31837
  headers: { "content-type": "application/json" },
31811
- body: JSON.stringify({ app_id: APP_ID, op: "agentRun", payload: payload }),
31838
+ body: JSON.stringify({ app_id: APP_ID, op: op, payload: payload }),
31812
31839
  signal: controller.signal,
31813
31840
  });
31814
31841
  if (!res.ok || !res.body) {
@@ -31816,6 +31843,9 @@ function buildWrapperPage(args) {
31816
31843
  try { detail = JSON.parse(detail).message || detail; } catch (_) {}
31817
31844
  throw new Error(detail || ("HTTP " + res.status));
31818
31845
  }
31846
+ // Forward the run id (poll-recovery / cancel / continue key on it).
31847
+ const runId = res.headers.get("x-app-agent-run-id");
31848
+ if (runId) iframe.contentWindow.postMessage({ id: id, type: "run-id", runId: runId }, VITE_ORIGIN);
31819
31849
  const reader = res.body.getReader();
31820
31850
  const decoder = new TextDecoder();
31821
31851
  for (;;) {
@@ -31849,7 +31879,7 @@ function buildWrapperPage(args) {
31849
31879
  }
31850
31880
  if (typeof msg.op !== "string") return;
31851
31881
  // Streaming agent run \u2014 many messages back, not a single result.
31852
- if (msg.op === "agentRun") { handleAgentRun(msg.id, msg.payload); return; }
31882
+ if (msg.op === "agentRun" || msg.op === "agentRunContinue") { handleAgentRun(msg.id, msg.op, msg.payload); return; }
31853
31883
  const startedAt = performance.now();
31854
31884
  try {
31855
31885
  const data = msg.op === "upload"
@@ -32088,18 +32118,37 @@ async function startDevServer(args) {
32088
32118
  });
32089
32119
  try {
32090
32120
  const body = await readJson(req);
32091
- const p = body.payload ?? {};
32092
- if (typeof p.alias !== "string" || typeof p.session_id !== "string") {
32093
- throw new Error("agentRun payload must include `alias` and `session_id`");
32094
- }
32095
- const upstream = await args.client.appAgentRunStream(
32096
- body.app_id,
32097
- p.alias,
32098
- { session_id: p.session_id, input: p.input ?? {} },
32099
- ac.signal
32100
- );
32121
+ let upstream;
32122
+ if (body.op === "agentRunContinue") {
32123
+ const p = body.payload ?? {};
32124
+ if (typeof p.run_id !== "string" || typeof p.tool_call_id !== "string" || typeof p.output !== "object" || p.output === null) {
32125
+ throw new Error("agentRunContinue payload must include `run_id`, `tool_call_id`, and `output`");
32126
+ }
32127
+ upstream = await args.client.appAgentRunContinueStream(
32128
+ body.app_id,
32129
+ p.run_id,
32130
+ { tool_call_id: p.tool_call_id, output: p.output },
32131
+ ac.signal
32132
+ );
32133
+ } else {
32134
+ const p = body.payload ?? {};
32135
+ if (typeof p.alias !== "string" || typeof p.session_id !== "string") {
32136
+ throw new Error("agentRun payload must include `alias` and `session_id`");
32137
+ }
32138
+ upstream = await args.client.appAgentRunStream(
32139
+ body.app_id,
32140
+ p.alias,
32141
+ { session_id: p.session_id, input: p.input ?? {} },
32142
+ ac.signal
32143
+ );
32144
+ }
32101
32145
  if (!upstream.body) throw new Error("agent run returned no stream body");
32102
- res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform" });
32146
+ const runId = upstream.headers.get("x-app-agent-run-id");
32147
+ res.writeHead(200, {
32148
+ "Content-Type": "text/event-stream",
32149
+ "Cache-Control": "no-cache, no-transform",
32150
+ ...runId ? { "x-app-agent-run-id": runId } : {}
32151
+ });
32103
32152
  const reader = upstream.body.getReader();
32104
32153
  for (; ; ) {
32105
32154
  const { value, done } = await reader.read();
@@ -32955,6 +33004,12 @@ var LOTICS_INCLUDE_GLOB = ".lotics/**/*";
32955
33004
  var STALE_LOTICS_INCLUDES = /* @__PURE__ */ new Set([".lotics", "./.lotics", ".lotics/"]);
32956
33005
  var FALLBACK_ENVELOPE_PREFIX = "async function __workflow(): Promise<__WorkflowReturn | void> {\n";
32957
33006
  var FALLBACK_ENVELOPE_SUFFIX = "\n}";
33007
+ function toWorkflowDtsDeclaration(d) {
33008
+ return { inputs: d.inputs, outputs: d.outputs };
33009
+ }
33010
+ function isUnknownWorkflowAliasError(err2) {
33011
+ return err2 instanceof Error && /^400:/.test(err2.message) && /has no workflow alias/i.test(err2.message);
33012
+ }
32958
33013
  function workflowFileHeader(alias) {
32959
33014
  const refPath = path5.join("..", "..", WORKFLOW_GLOBALS_DIR, `${alias}.globals.d.ts`).split(path5.sep).join("/");
32960
33015
  return `/// <reference path="${refPath}" />
@@ -33015,9 +33070,9 @@ function stripWorkflowHeader(content) {
33015
33070
  }
33016
33071
  return lines.slice(start + 1).join("\n").replace(/\s+$/, "");
33017
33072
  }
33018
- async function writeWorkflowFiles(client, projectDir, app_id, aliases) {
33073
+ async function writeWorkflowFiles(client, projectDir, app_id, workflows) {
33019
33074
  const written = [];
33020
- for (const alias of aliases) {
33075
+ for (const [alias, declaration] of Object.entries(workflows)) {
33021
33076
  const res = await client.getAppWorkflow(app_id, alias);
33022
33077
  const source = res.error || res.result === null || typeof res.result !== "object" ? null : res.result.source;
33023
33078
  if (typeof source !== "string" || source.trim() === "") {
@@ -33026,15 +33081,26 @@ async function writeWorkflowFiles(client, projectDir, app_id, aliases) {
33026
33081
  );
33027
33082
  continue;
33028
33083
  }
33029
- const envelope = await fetchWorkflowGlobals(client, projectDir, app_id, alias);
33084
+ const envelope = await fetchWorkflowGlobals(
33085
+ client,
33086
+ projectDir,
33087
+ app_id,
33088
+ alias,
33089
+ toWorkflowDtsDeclaration(declaration)
33090
+ );
33030
33091
  writeWorkflowFile(projectDir, alias, source, envelope);
33031
33092
  written.push(alias);
33032
33093
  }
33033
33094
  return written;
33034
33095
  }
33035
- async function fetchWorkflowGlobals(client, projectDir, app_id, alias) {
33096
+ async function fetchWorkflowGlobals(client, projectDir, app_id, alias, declaration) {
33036
33097
  try {
33037
- const { dts, envelope_prefix, envelope_suffix } = await client.getAppWorkflowDts(app_id, alias);
33098
+ const { dts, envelope_prefix, envelope_suffix } = await fetchWorkflowDts(
33099
+ client,
33100
+ app_id,
33101
+ alias,
33102
+ declaration
33103
+ );
33038
33104
  writeWorkflowGlobals(projectDir, alias, dts);
33039
33105
  return { prefix: envelope_prefix, suffix: envelope_suffix };
33040
33106
  } catch (err2) {
@@ -33044,6 +33110,16 @@ async function fetchWorkflowGlobals(client, projectDir, app_id, alias) {
33044
33110
  return { prefix: FALLBACK_ENVELOPE_PREFIX, suffix: FALLBACK_ENVELOPE_SUFFIX };
33045
33111
  }
33046
33112
  }
33113
+ async function fetchWorkflowDts(client, app_id, alias, declaration) {
33114
+ try {
33115
+ return await client.getAppWorkflowDts(app_id, alias);
33116
+ } catch (err2) {
33117
+ if (declaration && isUnknownWorkflowAliasError(err2)) {
33118
+ return client.getAppWorkflowDts(app_id, alias, declaration);
33119
+ }
33120
+ throw err2;
33121
+ }
33122
+ }
33047
33123
  function runTar(args, cwd) {
33048
33124
  return new Promise((resolve2, reject2) => {
33049
33125
  const proc = spawn2("tar", args, { cwd, stdio: ["ignore", "ignore", "pipe"] });
@@ -33090,10 +33166,37 @@ function readAppMeta(projectDir) {
33090
33166
  capabilities: pkg2.lotics.capabilities
33091
33167
  };
33092
33168
  }
33169
+ var APP_META_KEYS = Object.keys({
33170
+ app_id: true,
33171
+ workspace_id: true,
33172
+ current_version_id: true,
33173
+ version_number: true,
33174
+ workflows: true,
33175
+ queries: true,
33176
+ agents: true,
33177
+ capabilities: true
33178
+ });
33093
33179
  function writeAppMeta(projectDir, meta3) {
33094
33180
  const pkgPath2 = path5.join(projectDir, "package.json");
33095
33181
  const pkg2 = JSON.parse(fs4.readFileSync(pkgPath2, "utf-8"));
33096
- pkg2.lotics = meta3;
33182
+ const existing = pkg2.lotics && typeof pkg2.lotics === "object" ? pkg2.lotics : {};
33183
+ const preserved = {};
33184
+ for (const [key, value] of Object.entries(existing)) {
33185
+ if (!APP_META_KEYS.includes(key)) preserved[key] = value;
33186
+ }
33187
+ pkg2.lotics = { ...meta3, ...preserved };
33188
+ fs4.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
33189
+ }
33190
+ function writeWorkflowOutputs(projectDir, alias, outputs) {
33191
+ const pkgPath2 = path5.join(projectDir, "package.json");
33192
+ const pkg2 = JSON.parse(fs4.readFileSync(pkgPath2, "utf-8"));
33193
+ const declaration = pkg2.lotics?.workflows?.[alias];
33194
+ if (!declaration) {
33195
+ throw new Error(
33196
+ `package.json#lotics.workflows.${alias} disappeared before the derived-outputs write-back.`
33197
+ );
33198
+ }
33199
+ declaration.outputs = outputs;
33097
33200
  fs4.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
33098
33201
  }
33099
33202
  function ensureAppTsconfig(projectDir) {
@@ -33228,19 +33331,29 @@ async function appCodegen(args) {
33228
33331
  `\u26A0 Could not regenerate .lotics/app_fields.ts (${err2 instanceof Error ? err2.message : String(err2)}). Kept the existing file.`
33229
33332
  );
33230
33333
  }
33231
- await refreshWorkflowGlobals(args.client, projectDir, meta3.app_id, Object.keys(meta3.workflows ?? {}));
33334
+ await refreshWorkflowGlobals(args.client, projectDir, meta3.app_id, meta3.workflows ?? {});
33232
33335
  }
33233
- async function refreshWorkflowGlobals(client, projectDir, app_id, aliases) {
33234
- for (const alias of aliases) {
33235
- const file2 = workflowFilePath(projectDir, alias);
33236
- if (!fs4.existsSync(file2)) continue;
33237
- const body = stripWorkflowHeader(fs4.readFileSync(file2, "utf-8"));
33238
- if (body.trim() === "") continue;
33239
- const envelope = await fetchWorkflowGlobals(client, projectDir, app_id, alias);
33240
- writeWorkflowFile(projectDir, alias, body, envelope);
33241
- console.error(`Refreshed ${path5.relative(projectDir, file2)} + its workflow types`);
33336
+ async function refreshWorkflowGlobals(client, projectDir, app_id, workflows) {
33337
+ for (const [alias, declaration] of Object.entries(workflows)) {
33338
+ const refreshed = await refreshWorkflowTypes(
33339
+ client,
33340
+ projectDir,
33341
+ app_id,
33342
+ alias,
33343
+ toWorkflowDtsDeclaration(declaration)
33344
+ );
33345
+ if (refreshed) console.error(`Refreshed workflow types for ${alias}`);
33242
33346
  }
33243
33347
  }
33348
+ async function refreshWorkflowTypes(client, projectDir, app_id, alias, declaration) {
33349
+ const file2 = workflowFilePath(projectDir, alias);
33350
+ if (!fs4.existsSync(file2)) return false;
33351
+ const body = stripWorkflowHeader(fs4.readFileSync(file2, "utf-8"));
33352
+ if (body.trim() === "") return false;
33353
+ const envelope = await fetchWorkflowGlobals(client, projectDir, app_id, alias, declaration);
33354
+ writeWorkflowFile(projectDir, alias, body, envelope);
33355
+ return true;
33356
+ }
33244
33357
  function stampPulledManifest(projectDir, args) {
33245
33358
  writeAppMeta(projectDir, {
33246
33359
  app_id: args.app_id,
@@ -33403,9 +33516,9 @@ async function appPull(client, args) {
33403
33516
  queries: app.queries ?? {},
33404
33517
  agents: app.agents ?? {}
33405
33518
  });
33406
- const aliases = Object.keys(app.workflows ?? {});
33407
- if (aliases.length > 0) {
33408
- const written = await writeWorkflowFiles(client, targetPath, app.id, aliases);
33519
+ const workflows = app.workflows ?? {};
33520
+ if (Object.keys(workflows).length > 0) {
33521
+ const written = await writeWorkflowFiles(client, targetPath, app.id, workflows);
33409
33522
  if (written.length > 0) {
33410
33523
  console.error(
33411
33524
  `Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/ (${written.join(", ")})`
@@ -33807,8 +33920,19 @@ async function appWorkflowSet(client, args) {
33807
33920
  const result = res.result ?? {};
33808
33921
  const workflowId = typeof result.workflow_id === "string" ? result.workflow_id : "(unknown)";
33809
33922
  console.error(`Set workflow "${args.alias}" \u2192 ${workflowId}`);
33810
- if (result.outputs && typeof result.outputs === "object") {
33811
- console.error(` result.data schema: ${JSON.stringify(result.outputs)}`);
33923
+ const derivedOutputs = result.outputs !== null && typeof result.outputs === "object" && !Array.isArray(result.outputs) ? result.outputs : null;
33924
+ if (!declaration.outputs && derivedOutputs && Object.keys(derivedOutputs).length > 0) {
33925
+ writeWorkflowOutputs(projectDir, args.alias, derivedOutputs);
33926
+ console.error(
33927
+ ` Wrote the derived result.data schema into package.json#lotics.workflows.${args.alias}.outputs.`
33928
+ );
33929
+ const refreshed = await refreshWorkflowTypes(client, projectDir, meta3.app_id, args.alias, {
33930
+ inputs: declaration.inputs,
33931
+ outputs: derivedOutputs
33932
+ });
33933
+ if (refreshed) console.error(` Refreshed workflow types for ${args.alias}.`);
33934
+ } else if (derivedOutputs) {
33935
+ console.error(` result.data schema: ${JSON.stringify(derivedOutputs)}`);
33812
33936
  }
33813
33937
  }
33814
33938
  async function appQuerySet(client, args) {
@@ -33834,12 +33958,12 @@ async function appWorkflowPull(client) {
33834
33958
  const projectDir = process.cwd();
33835
33959
  const meta3 = readAppMeta(projectDir);
33836
33960
  const app = await client.getApp(meta3.app_id);
33837
- const aliases = Object.keys(app.workflows ?? {});
33838
- if (aliases.length === 0) {
33961
+ const workflows = app.workflows ?? {};
33962
+ if (Object.keys(workflows).length === 0) {
33839
33963
  console.error(`App ${meta3.app_id} has no bound workflows.`);
33840
33964
  return;
33841
33965
  }
33842
- const written = await writeWorkflowFiles(client, projectDir, meta3.app_id, aliases);
33966
+ const written = await writeWorkflowFiles(client, projectDir, meta3.app_id, workflows);
33843
33967
  console.error(
33844
33968
  `Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/` + (written.length > 0 ? ` (${written.join(", ")})` : "")
33845
33969
  );
@@ -47318,13 +47442,50 @@ function unmergeCellsCommand(workbook, sheetIndex, mergeRef) {
47318
47442
  }
47319
47443
  };
47320
47444
  }
47445
+ function isInertCell(cell) {
47446
+ return (cell.value === null || cell.value === "") && cell.formula === void 0 && cell.styleIndex === 0 && (cell.originalXfIndex === void 0 || cell.originalXfIndex === 0) && (cell.numFmtCode === "General" || cell.numFmtCode === "") && cell.richText === void 0 && cell.error === void 0 && cell.content.type === "plain" && cell.content.text === "";
47447
+ }
47448
+ function clampInertCells(sheet) {
47449
+ let minRow = Infinity;
47450
+ let maxRow = 0;
47451
+ let minCol = Infinity;
47452
+ let maxCol = 0;
47453
+ let hasMeaningful = false;
47454
+ for (const [ref, cell] of sheet.cells) {
47455
+ if (isInertCell(cell)) continue;
47456
+ const rc = refToRowColSafe(ref);
47457
+ if (!rc) continue;
47458
+ hasMeaningful = true;
47459
+ if (rc.row < minRow) minRow = rc.row;
47460
+ if (rc.row > maxRow) maxRow = rc.row;
47461
+ if (rc.col < minCol) minCol = rc.col;
47462
+ if (rc.col > maxCol) maxCol = rc.col;
47463
+ }
47464
+ const pruned = /* @__PURE__ */ new Map();
47465
+ for (const [ref, cell] of sheet.cells) {
47466
+ if (!isInertCell(cell)) continue;
47467
+ if (!hasMeaningful) {
47468
+ pruned.set(ref, cell);
47469
+ continue;
47470
+ }
47471
+ const rc = refToRowColSafe(ref);
47472
+ if (!rc) continue;
47473
+ if (rc.row < minRow || rc.row > maxRow || rc.col < minCol || rc.col > maxCol) {
47474
+ pruned.set(ref, cell);
47475
+ }
47476
+ }
47477
+ for (const ref of pruned.keys()) sheet.cells.delete(ref);
47478
+ return pruned;
47479
+ }
47321
47480
  function insertRowsCommand(workbook, sheetIndex, at, count) {
47322
47481
  const sheet = workbook.sheets[sheetIndex];
47323
47482
  let mergeSnapshot = [];
47483
+ let inertSnapshot = /* @__PURE__ */ new Map();
47324
47484
  return {
47325
47485
  description: `Insert ${count} row${count > 1 ? "s" : ""} at ${at}`,
47326
47486
  execute() {
47327
47487
  mergeSnapshot = [...sheet.mergedCells];
47488
+ inertSnapshot = clampInertCells(sheet);
47328
47489
  const toMove = [];
47329
47490
  for (const [ref] of sheet.cells) {
47330
47491
  const rc = refToRowColSafe(ref);
@@ -47368,6 +47529,7 @@ function insertRowsCommand(workbook, sheetIndex, at, count) {
47368
47529
  sheet.rowHeights.set(r - count, h);
47369
47530
  }
47370
47531
  for (let r = at; r < at + count; r++) sheet.rowHeights.delete(r);
47532
+ for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
47371
47533
  workbook.emit({ type: "rows_deleted", sheet: sheetIndex, at, count });
47372
47534
  }
47373
47535
  };
@@ -47382,10 +47544,12 @@ function deleteRowsCommand(workbook, sheetIndex, at, count) {
47382
47544
  }
47383
47545
  }
47384
47546
  let mergeSnapshot = [];
47547
+ let inertSnapshot = /* @__PURE__ */ new Map();
47385
47548
  return {
47386
47549
  description: `Delete ${count} row${count > 1 ? "s" : ""} at ${at}`,
47387
47550
  execute() {
47388
47551
  mergeSnapshot = [...sheet.mergedCells];
47552
+ inertSnapshot = clampInertCells(sheet);
47389
47553
  for (const ref of snapshot.keys()) sheet.cells.delete(ref);
47390
47554
  const toMove = [];
47391
47555
  for (const [ref] of sheet.cells) {
@@ -47418,6 +47582,7 @@ function deleteRowsCommand(workbook, sheetIndex, at, count) {
47418
47582
  for (const [ref, snap] of snapshot) {
47419
47583
  sheet.set(ref, snap.value, sheet.styles?.get(snap.styleIndex), snap.numFmtCode, snap.formula);
47420
47584
  }
47585
+ for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
47421
47586
  workbook.emit({ type: "rows_inserted", sheet: sheetIndex, at, count });
47422
47587
  }
47423
47588
  };
@@ -47425,10 +47590,12 @@ function deleteRowsCommand(workbook, sheetIndex, at, count) {
47425
47590
  function insertColsCommand(workbook, sheetIndex, at, count) {
47426
47591
  const sheet = workbook.sheets[sheetIndex];
47427
47592
  let mergeSnapshot = [];
47593
+ let inertSnapshot = /* @__PURE__ */ new Map();
47428
47594
  return {
47429
47595
  description: `Insert ${count} column${count > 1 ? "s" : ""} at ${at}`,
47430
47596
  execute() {
47431
47597
  mergeSnapshot = [...sheet.mergedCells];
47598
+ inertSnapshot = clampInertCells(sheet);
47432
47599
  const toMove = [];
47433
47600
  for (const [ref] of sheet.cells) {
47434
47601
  const rc = refToRowColSafe(ref);
@@ -47472,6 +47639,7 @@ function insertColsCommand(workbook, sheetIndex, at, count) {
47472
47639
  sheet.colWidths.set(c - count, w);
47473
47640
  }
47474
47641
  for (let c = at; c < at + count; c++) sheet.colWidths.delete(c);
47642
+ for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
47475
47643
  workbook.emit({ type: "cols_deleted", sheet: sheetIndex, at, count });
47476
47644
  }
47477
47645
  };
@@ -47486,10 +47654,12 @@ function deleteColsCommand(workbook, sheetIndex, at, count) {
47486
47654
  }
47487
47655
  }
47488
47656
  let mergeSnapshot = [];
47657
+ let inertSnapshot = /* @__PURE__ */ new Map();
47489
47658
  return {
47490
47659
  description: `Delete ${count} column${count > 1 ? "s" : ""} at ${at}`,
47491
47660
  execute() {
47492
47661
  mergeSnapshot = [...sheet.mergedCells];
47662
+ inertSnapshot = clampInertCells(sheet);
47493
47663
  for (const ref of snapshot.keys()) sheet.cells.delete(ref);
47494
47664
  const toMove = [];
47495
47665
  for (const [ref] of sheet.cells) {
@@ -47522,6 +47692,7 @@ function deleteColsCommand(workbook, sheetIndex, at, count) {
47522
47692
  for (const [ref, snap] of snapshot) {
47523
47693
  sheet.set(ref, snap.value, sheet.styles?.get(snap.styleIndex), snap.numFmtCode, snap.formula);
47524
47694
  }
47695
+ for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
47525
47696
  workbook.emit({ type: "cols_inserted", sheet: sheetIndex, at, count });
47526
47697
  }
47527
47698
  };
@@ -47744,26 +47915,37 @@ function lazyEngine(workbook) {
47744
47915
  }
47745
47916
  };
47746
47917
  }
47747
- function readToJson(filePath) {
47918
+ function readToJson(filePath, filter2) {
47748
47919
  const buffer = fs6.readFileSync(filePath);
47749
47920
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
47750
47921
  const parsed = parseExcelBuffer(arrayBuffer);
47922
+ let sheets = parsed.sheets;
47923
+ if (filter2?.sheet !== void 0) {
47924
+ const match = sheets.filter((s) => s.name === filter2.sheet);
47925
+ if (match.length === 0) {
47926
+ const names = parsed.sheets.map((s) => `"${s.name}"`).join(", ");
47927
+ fail(`Sheet not found: "${filter2.sheet}". Available: ${names}`);
47928
+ }
47929
+ sheets = match;
47930
+ }
47751
47931
  return {
47752
47932
  activeSheetIndex: parsed.activeSheetIndex,
47753
- sheets: parsed.sheets.map((s) => ({
47933
+ sheets: sheets.map((s) => ({
47754
47934
  name: s.name,
47755
47935
  totalRowCount: s.totalRowCount,
47756
47936
  truncated: s.truncated,
47757
- cells: cellsFromParsedRows(s.rows),
47937
+ cells: cellsFromParsedRows(s.rows, filter2?.range),
47758
47938
  merges: s.mergedCells.map((m) => `${rowColToRef(m.startRow, m.startCol)}:${rowColToRef(m.endRow, m.endCol)}`),
47759
47939
  freeze: s.freezePane ? { row: s.freezePane.frozenRows, col: s.freezePane.frozenCols } : void 0
47760
47940
  }))
47761
47941
  };
47762
47942
  }
47763
- function cellsFromParsedRows(rows) {
47943
+ function cellsFromParsedRows(rows, range) {
47764
47944
  const out = {};
47765
47945
  for (const row of rows) {
47946
+ if (range && (row.index < range.startRow || row.index > range.endRow)) continue;
47766
47947
  for (const cell of row.cells) {
47948
+ if (range && (cell.column < range.startCol || cell.column > range.endCol)) continue;
47767
47949
  const ref = rowColToRef(row.index, cell.column);
47768
47950
  const value = cell.typedValue ?? cell.value;
47769
47951
  const entry = { value };
@@ -47835,9 +48017,34 @@ function valueToInput(value) {
47835
48017
  if (value === null || value === void 0) return "";
47836
48018
  return String(value);
47837
48019
  }
47838
- function xlsxRead(filePath) {
47839
- if (!filePath) fail("Usage: lotics xlsx read <file>");
47840
- const data = readToJson(filePath);
48020
+ function readOptionFlag(rest, name) {
48021
+ const i2 = rest.indexOf(name);
48022
+ if (i2 < 0) return void 0;
48023
+ const val = rest[i2 + 1];
48024
+ if (val === void 0 || val.startsWith("--")) fail(`Missing value for ${name}`);
48025
+ return val;
48026
+ }
48027
+ function splitOptionalSheetRef(spec) {
48028
+ const idx = spec.indexOf("!");
48029
+ if (idx < 0) return { sheet: void 0, ref: spec };
48030
+ return { sheet: spec.slice(0, idx), ref: spec.slice(idx + 1) };
48031
+ }
48032
+ function xlsxRead(filePath, rest) {
48033
+ if (!filePath) fail("Usage: lotics xlsx read <file> [--sheet <name>] [--range <sheet>!<A1:G60>]");
48034
+ const rangeArg = readOptionFlag(rest, "--range");
48035
+ const sheetArg = readOptionFlag(rest, "--sheet");
48036
+ let filter2;
48037
+ if (rangeArg !== void 0) {
48038
+ const { sheet, ref } = splitOptionalSheetRef(rangeArg);
48039
+ const targetSheet = sheet ?? sheetArg;
48040
+ if (targetSheet === void 0) {
48041
+ fail("--range without a <sheet>! prefix requires --sheet <name>");
48042
+ }
48043
+ filter2 = { sheet: targetSheet, range: parseRange2(ref) };
48044
+ } else if (sheetArg !== void 0) {
48045
+ filter2 = { sheet: sheetArg };
48046
+ }
48047
+ const data = readToJson(filePath, filter2);
47841
48048
  console.log(JSON.stringify(data, null, 2));
47842
48049
  }
47843
48050
  function xlsxWrite(filePath, json2) {
@@ -48074,7 +48281,10 @@ function printXlsxHelp() {
48074
48281
  console.error(`Lotics xlsx commands \u2014 manipulate .xlsx files in place.
48075
48282
  Uses Lotics' own xlsx engine; round-trips faithfully with the Lotics editor and templates.
48076
48283
 
48077
- lotics xlsx read <file> Dump file as JSON (sheets, cells, merges, freeze)
48284
+ lotics xlsx read <file> [--sheet <name>] [--range <sheet>!<A1:G60>]
48285
+ Dump file as JSON (sheets, cells, merges, freeze).
48286
+ --sheet limits to one sheet; --range to a cell window
48287
+ (its <sheet>! prefix is optional when --sheet is given)
48078
48288
  lotics xlsx write <file> '<json>' Create .xlsx from {"sheets":[{"name","cells":{"A1":...}}]}
48079
48289
  lotics xlsx set-cell <file> <sheet>!<ref> '<v>' Set cell value (prefix '=' for formula)
48080
48290
  lotics xlsx clear-range <file> <sheet>!<range> Clear all cells in a range
@@ -48096,7 +48306,7 @@ Edit ops mutate the file atomically (temp file + rename).`);
48096
48306
  async function runXlsxCommand(subcommand, toolArgs, restArgs) {
48097
48307
  switch (subcommand) {
48098
48308
  case "read":
48099
- return xlsxRead(toolArgs);
48309
+ return xlsxRead(toolArgs, restArgs);
48100
48310
  case "write":
48101
48311
  return xlsxWrite(toolArgs, restArgs[0]);
48102
48312
  case "set-cell":
@@ -68204,8 +68414,14 @@ async function cdpConnect(wsUrl) {
68204
68414
  });
68205
68415
  return { send, close: () => ws.close() };
68206
68416
  }
68417
+ function isStoredFileId(target) {
68418
+ return /^fil_[A-Za-z0-9]+$/.test(target);
68419
+ }
68420
+ function defaultPreviewOutputPath(filename, cwd) {
68421
+ return join(cwd, basename(filename, extname(filename)) + ".png");
68422
+ }
68207
68423
  async function runPreviewCommand(filePath, flags) {
68208
- if (!filePath) fail2("Usage: lotics preview <file.docx|.xlsx> [--out <file.png>]");
68424
+ if (!filePath) fail2("Usage: lotics preview <file.docx|.xlsx | fil_id> [-o <file.png>]");
68209
68425
  const abs = resolve(filePath);
68210
68426
  if (!existsSync(abs)) fail2(`File not found: ${abs}`);
68211
68427
  const ext = extname(abs).toLowerCase();
@@ -68438,8 +68654,10 @@ COMMANDS
68438
68654
  lotics file download <file_id> Download a file by ID (alias: lotics download)
68439
68655
  lotics file download record <record_id> <field_key>
68440
68656
  Download all files on a record file field
68441
- lotics file preview <file> [-o png] Render a .docx/.xlsx to a PNG (frontend engines;
68442
- needs a Chrome/Chromium on the machine)
68657
+ lotics file preview <file|fil_id> [-o png]
68658
+ Render a .docx/.xlsx to a PNG \u2014 a local path OR a
68659
+ stored file id (downloaded first). Frontend engines;
68660
+ needs a Chrome/Chromium on the machine
68443
68661
 
68444
68662
  FLAGS
68445
68663
  --json Full JSON output (default is human-readable text)
@@ -68799,6 +69017,18 @@ async function main() {
68799
69017
  return;
68800
69018
  }
68801
69019
  if (command === "preview") {
69020
+ if (subcommand && isStoredFileId(subcommand)) {
69021
+ const { client: client2 } = requireClient(flags);
69022
+ const tmpDir = fs9.mkdtempSync(path7.join(os2.tmpdir(), "lotics-preview-"));
69023
+ try {
69024
+ const { path: localPath, filename } = await client2.downloadFileById(subcommand, tmpDir);
69025
+ const output = flags.output ?? defaultPreviewOutputPath(filename, process.cwd());
69026
+ await runPreviewCommand(localPath, { output });
69027
+ } finally {
69028
+ fs9.rmSync(tmpDir, { recursive: true, force: true });
69029
+ }
69030
+ return;
69031
+ }
68802
69032
  await runPreviewCommand(subcommand, flags);
68803
69033
  return;
68804
69034
  }
@@ -883,8 +883,17 @@ export declare class LoticsClient {
883
883
  * `async function __workflow(): …` wrapper the server compiles inside, so the
884
884
  * local typecheck mirrors the set-time verdict. Mirrors
885
885
  * POST /v1/apps/{app_id}/workflows/{alias}/dts.
886
+ *
887
+ * `declaration` (the manifest's `{ inputs?, outputs? }`) is posted as the body
888
+ * `{ declaration }` ONLY when the alias isn't `set` on the server yet — the
889
+ * server then synthesizes the dts from the declared schemas instead of 400ing
890
+ * "no workflow alias". A registered alias needs no declaration (the server's
891
+ * own bound contract wins), so the field is omitted in that case.
886
892
  */
887
- getAppWorkflowDts(app_id: string, alias: string): Promise<{
893
+ getAppWorkflowDts(app_id: string, alias: string, declaration?: {
894
+ inputs?: Record<string, unknown>;
895
+ outputs?: Record<string, unknown>;
896
+ }): Promise<{
888
897
  dts: string;
889
898
  envelope_prefix: string;
890
899
  envelope_suffix: string;
@@ -899,6 +908,16 @@ export declare class LoticsClient {
899
908
  session_id: string;
900
909
  input: Record<string, unknown>;
901
910
  }, signal?: AbortSignal): Promise<Response>;
911
+ /**
912
+ * Continue a PARKED (`awaiting_input`) agent run with the user's answer to its
913
+ * pending `ask_user_choice` — returns the RAW streamed continuation `Response`,
914
+ * exactly like `appAgentRunStream`. Mirrors
915
+ * POST /v1/apps/{app_id}/agent-runs/{run_id}/continue.
916
+ */
917
+ appAgentRunContinueStream(app_id: string, run_id: string, body: {
918
+ tool_call_id: string;
919
+ output: Record<string, unknown>;
920
+ }, signal?: AbortSignal): Promise<Response>;
902
921
  /**
903
922
  * A session's app-agent run history, oldest-first (the run just started is the
904
923
  * last, and its exact id is on the stream response's `x-app-agent-run-id`
@@ -608,9 +608,15 @@ export class LoticsClient {
608
608
  * `async function __workflow(): …` wrapper the server compiles inside, so the
609
609
  * local typecheck mirrors the set-time verdict. Mirrors
610
610
  * POST /v1/apps/{app_id}/workflows/{alias}/dts.
611
+ *
612
+ * `declaration` (the manifest's `{ inputs?, outputs? }`) is posted as the body
613
+ * `{ declaration }` ONLY when the alias isn't `set` on the server yet — the
614
+ * server then synthesizes the dts from the declared schemas instead of 400ing
615
+ * "no workflow alias". A registered alias needs no declaration (the server's
616
+ * own bound contract wins), so the field is omitted in that case.
611
617
  */
612
- async getAppWorkflowDts(app_id, alias) {
613
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/dts`);
618
+ async getAppWorkflowDts(app_id, alias, declaration) {
619
+ return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/dts`, declaration ? { declaration } : undefined);
614
620
  }
615
621
  /**
616
622
  * Open a streaming agent run and return the RAW streamed `Response` (the
@@ -629,6 +635,23 @@ export class LoticsClient {
629
635
  await this.throwResponseError(res);
630
636
  return res;
631
637
  }
638
+ /**
639
+ * Continue a PARKED (`awaiting_input`) agent run with the user's answer to its
640
+ * pending `ask_user_choice` — returns the RAW streamed continuation `Response`,
641
+ * exactly like `appAgentRunStream`. Mirrors
642
+ * POST /v1/apps/{app_id}/agent-runs/{run_id}/continue.
643
+ */
644
+ async appAgentRunContinueStream(app_id, run_id, body, signal) {
645
+ const res = await fetch(`${this.baseUrl}/v1/apps/${encodeURIComponent(app_id)}/agent-runs/${encodeURIComponent(run_id)}/continue`, {
646
+ method: "POST",
647
+ headers: { ...this.buildHeaders(), "Content-Type": "application/json" },
648
+ body: JSON.stringify(body),
649
+ signal,
650
+ });
651
+ if (!res.ok)
652
+ await this.throwResponseError(res);
653
+ return res;
654
+ }
632
655
  /**
633
656
  * A session's app-agent run history, oldest-first (the run just started is the
634
657
  * last, and its exact id is on the stream response's `x-app-agent-run-id`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.93.0",
3
+ "version": "0.94.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {