@lotics/cli 0.136.1 → 0.143.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
@@ -26822,12 +26822,12 @@ var require_tokens = __commonJS({
26822
26822
  });
26823
26823
  }
26824
26824
  exports2.assignCategoriesMapProp = assignCategoriesMapProp;
26825
- function singleAssignCategoriesToksMap(path10, nextNode) {
26826
- utils_1.forEach(path10, function(pathNode) {
26825
+ function singleAssignCategoriesToksMap(path12, nextNode) {
26826
+ utils_1.forEach(path12, function(pathNode) {
26827
26827
  nextNode.categoryMatchesMap[pathNode.tokenTypeIdx] = true;
26828
26828
  });
26829
26829
  utils_1.forEach(nextNode.CATEGORIES, function(nextCategory) {
26830
- var newPath = path10.concat(nextNode);
26830
+ var newPath = path12.concat(nextNode);
26831
26831
  if (!utils_1.contains(newPath, nextCategory)) {
26832
26832
  singleAssignCategoriesToksMap(newPath, nextCategory);
26833
26833
  }
@@ -28590,10 +28590,10 @@ var require_interpreter = __commonJS({
28590
28590
  /** @class */
28591
28591
  (function(_super) {
28592
28592
  __extends(AbstractNextPossibleTokensWalker2, _super);
28593
- function AbstractNextPossibleTokensWalker2(topProd, path10) {
28593
+ function AbstractNextPossibleTokensWalker2(topProd, path12) {
28594
28594
  var _this = _super.call(this) || this;
28595
28595
  _this.topProd = topProd;
28596
- _this.path = path10;
28596
+ _this.path = path12;
28597
28597
  _this.possibleTokTypes = [];
28598
28598
  _this.nextProductionName = "";
28599
28599
  _this.nextProductionOccurrence = 0;
@@ -28647,9 +28647,9 @@ var require_interpreter = __commonJS({
28647
28647
  /** @class */
28648
28648
  (function(_super) {
28649
28649
  __extends(NextAfterTokenWalker2, _super);
28650
- function NextAfterTokenWalker2(topProd, path10) {
28651
- var _this = _super.call(this, topProd, path10) || this;
28652
- _this.path = path10;
28650
+ function NextAfterTokenWalker2(topProd, path12) {
28651
+ var _this = _super.call(this, topProd, path12) || this;
28652
+ _this.path = path12;
28653
28653
  _this.nextTerminalName = "";
28654
28654
  _this.nextTerminalOccurrence = 0;
28655
28655
  _this.nextTerminalName = _this.path.lastTok.name;
@@ -29346,10 +29346,10 @@ var require_lookahead = __commonJS({
29346
29346
  }
29347
29347
  return result;
29348
29348
  }
29349
- function pathToHashKeys(path10) {
29349
+ function pathToHashKeys(path12) {
29350
29350
  var keys3 = [""];
29351
- for (var i2 = 0; i2 < path10.length; i2++) {
29352
- var tokType = path10[i2];
29351
+ for (var i2 = 0; i2 < path12.length; i2++) {
29352
+ var tokType = path12[i2];
29353
29353
  var longerKeys = [];
29354
29354
  for (var j = 0; j < keys3.length; j++) {
29355
29355
  var currShorterKey = keys3[j];
@@ -29661,9 +29661,9 @@ var require_checks = __commonJS({
29661
29661
  return errors2;
29662
29662
  }
29663
29663
  exports2.validateRuleIsOverridden = validateRuleIsOverridden;
29664
- function validateNoLeftRecursion(topRule, currRule, errMsgProvider, path10) {
29665
- if (path10 === void 0) {
29666
- path10 = [];
29664
+ function validateNoLeftRecursion(topRule, currRule, errMsgProvider, path12) {
29665
+ if (path12 === void 0) {
29666
+ path12 = [];
29667
29667
  }
29668
29668
  var errors2 = [];
29669
29669
  var nextNonTerminals = getFirstNoneTerminal(currRule.definition);
@@ -29676,15 +29676,15 @@ var require_checks = __commonJS({
29676
29676
  errors2.push({
29677
29677
  message: errMsgProvider.buildLeftRecursionError({
29678
29678
  topLevelRule: topRule,
29679
- leftRecursionPath: path10
29679
+ leftRecursionPath: path12
29680
29680
  }),
29681
29681
  type: parser_1.ParserDefinitionErrorType.LEFT_RECURSION,
29682
29682
  ruleName
29683
29683
  });
29684
29684
  }
29685
- var validNextSteps = utils2.difference(nextNonTerminals, path10.concat([topRule]));
29685
+ var validNextSteps = utils2.difference(nextNonTerminals, path12.concat([topRule]));
29686
29686
  var errorsFromNextSteps = utils2.map(validNextSteps, function(currRefRule) {
29687
- var newPath = utils2.cloneArr(path10);
29687
+ var newPath = utils2.cloneArr(path12);
29688
29688
  newPath.push(currRefRule);
29689
29689
  return validateNoLeftRecursion(topRule, currRefRule, errMsgProvider, newPath);
29690
29690
  });
@@ -37293,8 +37293,8 @@ var require_utils4 = __commonJS({
37293
37293
  var result = transform2[inputType][outputType](input);
37294
37294
  return result;
37295
37295
  };
37296
- exports2.resolve = function(path10) {
37297
- var parts = path10.split("/");
37296
+ exports2.resolve = function(path12) {
37297
+ var parts = path12.split("/");
37298
37298
  var result = [];
37299
37299
  for (var index = 0; index < parts.length; index++) {
37300
37300
  var part = parts[index];
@@ -43147,18 +43147,18 @@ var require_object = __commonJS({
43147
43147
  var object2 = new ZipObject(name2, zipObjectContent, o);
43148
43148
  this.files[name2] = object2;
43149
43149
  };
43150
- var parentFolder = function(path10) {
43151
- if (path10.slice(-1) === "/") {
43152
- path10 = path10.substring(0, path10.length - 1);
43150
+ var parentFolder = function(path12) {
43151
+ if (path12.slice(-1) === "/") {
43152
+ path12 = path12.substring(0, path12.length - 1);
43153
43153
  }
43154
- var lastSlash = path10.lastIndexOf("/");
43155
- return lastSlash > 0 ? path10.substring(0, lastSlash) : "";
43154
+ var lastSlash = path12.lastIndexOf("/");
43155
+ return lastSlash > 0 ? path12.substring(0, lastSlash) : "";
43156
43156
  };
43157
- var forceTrailingSlash = function(path10) {
43158
- if (path10.slice(-1) !== "/") {
43159
- path10 += "/";
43157
+ var forceTrailingSlash = function(path12) {
43158
+ if (path12.slice(-1) !== "/") {
43159
+ path12 += "/";
43160
43160
  }
43161
- return path10;
43161
+ return path12;
43162
43162
  };
43163
43163
  var folderAdd = function(name2, createFolders) {
43164
43164
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults2.createFolders;
@@ -44160,9 +44160,9 @@ var require_lib4 = __commonJS({
44160
44160
  // src/cli.ts
44161
44161
  import dns from "node:dns";
44162
44162
  import net2 from "node:net";
44163
- import fs11 from "node:fs";
44163
+ import fs13 from "node:fs";
44164
44164
  import os4 from "node:os";
44165
- import path9 from "node:path";
44165
+ import path11 from "node:path";
44166
44166
  import readline from "node:readline";
44167
44167
 
44168
44168
  // ../shared/src/transport_error.ts
@@ -44345,8 +44345,8 @@ var LoticsClient = class {
44345
44345
  }
44346
44346
  return headers;
44347
44347
  }
44348
- async request(method, path10, body) {
44349
- const url2 = `${this.baseUrl}${path10}`;
44348
+ async request(method, path12, body) {
44349
+ const url2 = `${this.baseUrl}${path12}`;
44350
44350
  const headers = this.buildHeaders();
44351
44351
  const init = { method, headers };
44352
44352
  if (body !== void 0) {
@@ -44360,6 +44360,14 @@ var LoticsClient = class {
44360
44360
  async whoami() {
44361
44361
  return this.request("GET", "/v1/cli/whoami");
44362
44362
  }
44363
+ /**
44364
+ * File a hand-written report. Unlike a telemetry flush, the headers this
44365
+ * stamps are CORRECT: the invocation making the request is the one the report
44366
+ * is about, so the courier and the cargo are the same session.
44367
+ */
44368
+ async sendReport(body) {
44369
+ return this.request("POST", "/v1/cli/report", body);
44370
+ }
44363
44371
  setWorkspaceId(id) {
44364
44372
  this.workspaceId = id;
44365
44373
  }
@@ -45614,9 +45622,422 @@ var pkgPath = path5.resolve(path5.dirname(fileURLToPath(import.meta.url)), "../.
45614
45622
  var pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
45615
45623
  var VERSION = pkg.version;
45616
45624
 
45617
- // src/app_commands.ts
45625
+ // src/docs_command.ts
45626
+ import fs5 from "node:fs";
45627
+ import path6 from "node:path";
45628
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
45629
+ var READING_ORDER = ["@lotics/app-sdk", "@lotics/ui", "@lotics/cli"];
45630
+ function firstHeading(file2) {
45631
+ let text;
45632
+ try {
45633
+ text = fs5.readFileSync(file2, "utf-8");
45634
+ } catch {
45635
+ return "";
45636
+ }
45637
+ for (const line of text.split("\n", 40)) {
45638
+ const m = /^#\s+(.+?)\s*$/.exec(line);
45639
+ if (m) return m[1];
45640
+ }
45641
+ return "";
45642
+ }
45643
+ function readVersion(pkgDir) {
45644
+ try {
45645
+ const raw = fs5.readFileSync(path6.join(pkgDir, "package.json"), "utf-8");
45646
+ return JSON.parse(raw).version ?? "?";
45647
+ } catch {
45648
+ return "?";
45649
+ }
45650
+ }
45651
+ function ownPackageDir() {
45652
+ return path6.resolve(path6.dirname(fileURLToPath2(import.meta.url)), "../..");
45653
+ }
45654
+ function referencePackageDirs(projectDir) {
45655
+ const found = /* @__PURE__ */ new Map();
45656
+ for (let dir = projectDir; ; ) {
45657
+ const scope = path6.join(dir, "node_modules", "@lotics");
45658
+ if (fs5.existsSync(scope)) {
45659
+ for (const name2 of fs5.readdirSync(scope)) {
45660
+ const pkgDir = path6.join(scope, name2);
45661
+ const carries = fs5.existsSync(path6.join(pkgDir, "AGENTS.md")) || fs5.existsSync(path6.join(pkgDir, "docs"));
45662
+ if (carries && !found.has(`@lotics/${name2}`)) found.set(`@lotics/${name2}`, pkgDir);
45663
+ }
45664
+ }
45665
+ const parent = path6.dirname(dir);
45666
+ if (parent === dir) break;
45667
+ dir = parent;
45668
+ }
45669
+ const own = ownPackageDir();
45670
+ if (!found.has("@lotics/cli") && fs5.existsSync(path6.join(own, "AGENTS.md"))) {
45671
+ found.set("@lotics/cli", own);
45672
+ }
45673
+ return [...found.entries()].map(([pkg2, dir]) => ({ pkg: pkg2, dir })).sort((a, b) => {
45674
+ const ia = READING_ORDER.indexOf(a.pkg);
45675
+ const ib = READING_ORDER.indexOf(b.pkg);
45676
+ if (ia !== ib) return (ia < 0 ? READING_ORDER.length : ia) - (ib < 0 ? READING_ORDER.length : ib);
45677
+ return a.pkg.localeCompare(b.pkg);
45678
+ });
45679
+ }
45680
+ function collectDocAreas(projectDir) {
45681
+ const found = [];
45682
+ for (const { pkg: pkg2, dir } of referencePackageDirs(projectDir)) {
45683
+ const version2 = readVersion(dir);
45684
+ const index = path6.join(dir, "AGENTS.md");
45685
+ if (fs5.existsSync(index)) {
45686
+ found.push({
45687
+ area: pkg2.replace("@lotics/", ""),
45688
+ pkg: pkg2,
45689
+ version: version2,
45690
+ absPath: index,
45691
+ title: firstHeading(index)
45692
+ });
45693
+ }
45694
+ const docsDir = path6.join(dir, "docs");
45695
+ if (!fs5.existsSync(docsDir)) continue;
45696
+ for (const file2 of fs5.readdirSync(docsDir).sort()) {
45697
+ if (!file2.endsWith(".md")) continue;
45698
+ const abs2 = path6.join(docsDir, file2);
45699
+ found.push({
45700
+ area: file2.replace(/\.md$/, ""),
45701
+ pkg: pkg2,
45702
+ version: version2,
45703
+ absPath: abs2,
45704
+ title: firstHeading(abs2)
45705
+ });
45706
+ }
45707
+ }
45708
+ return found;
45709
+ }
45710
+ function qualified(a) {
45711
+ return `${a.pkg.replace("@lotics/", "")}/${a.area}`;
45712
+ }
45713
+ function matchDocArea(areas, query) {
45714
+ const q = query.trim().toLowerCase().replace(/\.md$/, "");
45715
+ const exact = areas.filter((a) => a.area.toLowerCase() === q || qualified(a).toLowerCase() === q);
45716
+ if (exact.length > 0) return exact;
45717
+ return areas.filter((a) => a.area.toLowerCase().includes(q));
45718
+ }
45719
+ function onlySelfReference(areas) {
45720
+ return areas.length > 0 && areas.every((a) => a.pkg === "@lotics/cli");
45721
+ }
45722
+ function renderDocIndex(areas) {
45723
+ if (areas.length === 0) {
45724
+ return "No reference packages resolved here.\n The references ship INSIDE the packages, so they appear once a project installs them:\n @lotics/app-sdk, @lotics/ui \u2014 run this from an app directory (see `lotics app create`).";
45725
+ }
45726
+ const lines = [];
45727
+ let currentPkg = "";
45728
+ for (const a of areas) {
45729
+ if (a.pkg !== currentPkg) {
45730
+ currentPkg = a.pkg;
45731
+ lines.push(`
45732
+ ${a.pkg} ${a.version}`);
45733
+ }
45734
+ const name2 = a.absPath.endsWith("AGENTS.md") ? `${a.area} \u2190 start here` : a.area;
45735
+ lines.push(` ${name2.padEnd(24)}${a.title}`);
45736
+ }
45737
+ lines.push("\n lotics docs <area> print one, e.g. `lotics docs ai`");
45738
+ lines.push(" lotics docs <pkg>/<area> when two packages share a name");
45739
+ lines.push("\n Versions are the INSTALLED ones; a doc describes the version beside it.");
45740
+ if (onlySelfReference(areas)) {
45741
+ lines.push(
45742
+ "\n Only this CLI's own reference resolved here \u2014 the @lotics/app-sdk and @lotics/ui\n references are NOT among them. They ship inside those packages, so they appear\n once a project installs them: `lotics app create <name>` scaffolds one with both."
45743
+ );
45744
+ }
45745
+ return lines.join("\n");
45746
+ }
45747
+ function docsCommand(args) {
45748
+ const projectDir = path6.resolve(args.projectDir ?? process.cwd());
45749
+ const areas = collectDocAreas(projectDir);
45750
+ if (args.area === void 0 || args.area === "") {
45751
+ console.log(renderDocIndex(areas));
45752
+ return;
45753
+ }
45754
+ const hits = matchDocArea(areas, args.area);
45755
+ if (hits.length === 0) {
45756
+ console.error(
45757
+ `No reference area matching "${args.area}".
45758
+ Run \`lotics docs\` for what is installed here.`
45759
+ );
45760
+ process.exit(1);
45761
+ }
45762
+ if (hits.length > 1) {
45763
+ console.error(
45764
+ `"${args.area}" matches more than one reference \u2014 name the package:
45765
+ ` + hits.map((a) => ` \u2022 lotics docs ${qualified(a)} (${a.pkg} ${a.version})`).join("\n")
45766
+ );
45767
+ process.exit(1);
45768
+ }
45769
+ const hit = hits[0];
45770
+ console.error(`${hit.pkg} ${hit.version} \xB7 ${path6.relative(projectDir, hit.absPath)}
45771
+ `);
45772
+ process.stdout.write(fs5.readFileSync(hit.absPath, "utf-8"));
45773
+ }
45774
+
45775
+ // src/commands.ts
45776
+ var COMMANDS = [
45777
+ {
45778
+ verbs: ["org"],
45779
+ help: [
45780
+ " lotics org List saved orgs (marks active)",
45781
+ " lotics org use <name|id> [--local] Switch active org (--local pins this dir)"
45782
+ ]
45783
+ },
45784
+ {
45785
+ verbs: ["workspace"],
45786
+ help: [
45787
+ " lotics workspace List workspaces in the active org (marks current)",
45788
+ " lotics workspace select <id> Switch to a different workspace",
45789
+ " lotics workspace create <name> Create a new workspace (admin only)",
45790
+ " lotics workspace delete <id> --yes Delete a workspace (admin only; soft delete, recoverable)",
45791
+ " lotics workspace doctor Report dangling schema references (admin only)"
45792
+ ]
45793
+ },
45794
+ {
45795
+ verbs: ["tools"],
45796
+ help: [
45797
+ " lotics tools List all available tools",
45798
+ " lotics tools <name> Show tool description and input schema"
45799
+ ]
45800
+ },
45801
+ {
45802
+ verbs: ["install"],
45803
+ help: [" lotics install <package_id> Materialize a published package into this workspace"]
45804
+ },
45805
+ {
45806
+ verbs: ["upgrade"],
45807
+ help: [" lotics upgrade Take the installed package's next version"]
45808
+ },
45809
+ {
45810
+ verbs: ["docs"],
45811
+ help: [
45812
+ " lotics docs List the reference docs of the INSTALLED packages",
45813
+ " lotics docs <area> Print one (e.g. 'lotics docs ai')"
45814
+ ]
45815
+ },
45816
+ {
45817
+ verbs: ["report"],
45818
+ help: [
45819
+ " lotics report '<json>' Tell the Lotics team what got in your way \u2014 a",
45820
+ " capability that is missing, a command that",
45821
+ " succeeded but did the wrong thing, an error that",
45822
+ " did not say how to fix it. Nothing else records",
45823
+ " these. A frame, not a paragraph: {goal, actual,",
45824
+ " expected?, tried?, wanted?} \u2014 run it bare for the",
45825
+ " full prompt. Also @file.json, or '-' for stdin"
45826
+ ]
45827
+ },
45828
+ {
45829
+ verbs: ["run"],
45830
+ help: [
45831
+ " lotics run <tool> '<json>' Execute a tool",
45832
+ " lotics run <tool> @args.json Read JSON args from a file (large payloads)",
45833
+ " cat args.json | lotics run <tool> Read JSON args from stdin (large payloads)"
45834
+ ]
45835
+ },
45836
+ {
45837
+ verbs: ["app"],
45838
+ help: [
45839
+ " lotics app create <name> [path] Create a new custom-code app + scaffold locally",
45840
+ " lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)",
45841
+ " lotics app deploy -m <message> Build + upload current dir as a new version \u2014 COMMIT",
45842
+ " (-m is required \u2014 it's the version's audit trail;",
45843
+ " carries code + queries only \u2014 workflow bindings are",
45844
+ " managed by set_app_workflow / remove_app_workflow)",
45845
+ " lotics app versions [app_id] Show deploy history newest-first (version,",
45846
+ " timestamp, deployer, build status, -m message;",
45847
+ " * marks the currently served version)",
45848
+ " lotics app codegen [path] Regenerate .lotics/* (types + field/option ids)",
45849
+ " from the manifest + workspace schema \u2014 no deploy",
45850
+ " lotics app check Run every deploy pre-flight WITHOUT building or",
45851
+ " shipping: agent schemas vs the live app, workflow",
45852
+ " declarations and BODIES vs what is live, aliases",
45853
+ " the code calls but nothing bound, undeclared",
45854
+ " capabilities, query drift. Exits 1 on what a",
45855
+ " deploy would refuse, so CI can gate on it",
45856
+ " lotics app workflow run <alias> '<json>' Execute a bound app workflow end-to-end",
45857
+ " (inputs: inline JSON, @file, or stdin;",
45858
+ " --print-created reports created records +",
45859
+ " a paste-ready cleanup plan; --cleanup also",
45860
+ " deletes those records \u2014 NOT a rollback)",
45861
+ " lotics app workflow set <alias> Push the edited src/workflows/<alias>.ts body",
45862
+ " through set_app_workflow (server verifies)",
45863
+ " lotics app workflow pull Rewrite src/workflows/*.ts from the server",
45864
+ " lotics app workflow check [alias] Typecheck src/workflows bodies locally (one",
45865
+ " isolated program per alias; the app's own tsc)",
45866
+ " lotics app query set <alias> Push package.json#lotics.queries.<alias> to",
45867
+ " apps.queries via set_app_query (no deploy;",
45868
+ " re-synced by the next deploy from the manifest)",
45869
+ " lotics app agent run <app_id> <alias> '<json>' Run a bound app agent end-to-end",
45870
+ " (inputs: inline JSON, @file, or stdin; streams",
45871
+ " progress to stderr, reports the settled run;",
45872
+ " --session <id> continues a thread; --json)",
45873
+ " lotics app agent set <alias> Push the edited src/agents/<alias>.md instructions,",
45874
+ " and ONLY those \u2014 the server merges, so the typed",
45875
+ " fields keep whatever is bound (a manifest is a",
45876
+ " snapshot; replaying it would revert them). Change",
45877
+ " those with set_app_agent, then pull.",
45878
+ " lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address",
45879
+ ` lotics app rename "<new name>" Rename the app's display name (launcher title)`,
45880
+ " lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)"
45881
+ ]
45882
+ },
45883
+ {
45884
+ verbs: ["knowledge"],
45885
+ help: [
45886
+ " lotics knowledge list List knowledge docs (id, name, description)",
45887
+ " lotics knowledge create --name <n> [--description <d>] --from <file.md>",
45888
+ " Create a doc from a file (or --content <str>);",
45889
+ " prints the new id",
45890
+ " lotics knowledge get <id> [-o <file.md>]",
45891
+ " Fetch a doc's content (to a file, or stdout;",
45892
+ " --json for the full doc)",
45893
+ " lotics knowledge update <id> [--from <file.md> | --content <str>] [--name <n>] [--description <d>]",
45894
+ " Update a doc \u2014 sends only the fields you pass",
45895
+ " lotics knowledge rm <id> Archive a doc"
45896
+ ]
45897
+ },
45898
+ {
45899
+ verbs: ["xlsx"],
45900
+ help: [
45901
+ " lotics xlsx <subcommand> ... Read/write/edit .xlsx files on your local filesystem",
45902
+ " (uses the bundled Lotics xlsx engine; prefer over",
45903
+ " npm xlsx/exceljs for round-trip fidelity)"
45904
+ ]
45905
+ },
45906
+ {
45907
+ verbs: ["docx"],
45908
+ help: [
45909
+ " lotics docx <subcommand> ... Read/write/edit .docx files on your local filesystem",
45910
+ " (uses the bundled Lotics OOXML engine; preserves",
45911
+ " tables, images, and unknown markup verbatim)"
45912
+ ]
45913
+ },
45914
+ {
45915
+ // `file` is a noun-group whose three verbs also stand alone.
45916
+ verbs: ["file"],
45917
+ aliases: ["upload", "download", "preview"],
45918
+ help: [
45919
+ " lotics file upload <file|dir...> Upload files (alias: lotics upload)",
45920
+ " lotics file download <file_id> Download a file by ID (alias: lotics download)",
45921
+ " lotics file download record <record_id> <field_key>",
45922
+ " Download all files on a record file field",
45923
+ " lotics file preview <file|fil_id> [-o png]",
45924
+ " Render a .docx/.xlsx to a PNG \u2014 a local path OR a",
45925
+ " stored file id (downloaded first). Frontend engines;",
45926
+ " needs a Chrome/Chromium on the machine"
45927
+ ]
45928
+ }
45929
+ ];
45930
+ function renderCommandsHelp() {
45931
+ return COMMANDS.flatMap((group) => group.help).join("\n");
45932
+ }
45933
+ function knownVerbs() {
45934
+ return new Set(COMMANDS.flatMap((group) => [...group.verbs, ...group.aliases ?? []]));
45935
+ }
45936
+
45937
+ // src/report_command.ts
45618
45938
  import fs6 from "node:fs";
45619
45939
  import path7 from "node:path";
45940
+ var LIMITS = { goal: 2e3, expected: 2e3, actual: 4e3, tried: 4e3, wanted: 2e3 };
45941
+ var REPORT_USAGE = `Usage: lotics report '<json>' lotics report @report.json \u2026 | lotics report -
45942
+
45943
+ Tell the Lotics team what got in your way. Most valuable, in order:
45944
+
45945
+ \u2022 A capability that seems missing \u2014 "there is no way to <X>".
45946
+ Nothing else can see this: no command ran, so nothing was recorded.
45947
+ \u2022 A command that succeeded but did the wrong thing. Exit 0 hides it completely.
45948
+ \u2022 An error message that did not name the remedy.
45949
+ \u2022 Anything that made authoring slower than it should have been.
45950
+
45951
+ The report is a frame, not a paragraph \u2014 these are the facts that exist only
45952
+ where you are sitting. The commands you ran attach themselves; do not retype them.
45953
+
45954
+ {
45955
+ "goal": "what you were trying to accomplish", (required)
45956
+ "actual": "what happened instead", (required)
45957
+ "expected": "what you expected \u2014 the gap is the defect",
45958
+ "tried": "what you already tried, and what it said",
45959
+ "wanted": "what would have unblocked you"
45960
+ }
45961
+
45962
+ There is no severity or category to pick. How bad and how common are read off
45963
+ the corpus; what you were trying to do is not recoverable from anywhere else.
45964
+
45965
+ Do not paste customer records, file contents, or credentials.`;
45966
+ function parseReport(raw) {
45967
+ const text = raw.trim();
45968
+ if (text === "") return { error: "Nothing to report." };
45969
+ let parsed;
45970
+ try {
45971
+ parsed = JSON.parse(text);
45972
+ } catch {
45973
+ return {
45974
+ error: text.startsWith("{") ? "That is not valid JSON." : "A report is a JSON object, not a sentence \u2014 the frame below is what makes it debuggable."
45975
+ };
45976
+ }
45977
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
45978
+ return { error: "A report is a JSON object." };
45979
+ }
45980
+ const fields = parsed;
45981
+ const read = (key) => {
45982
+ const value2 = fields[key];
45983
+ if (value2 === void 0 || value2 === null) return void 0;
45984
+ if (typeof value2 !== "string") return void 0;
45985
+ const trimmed = value2.trim();
45986
+ return trimmed === "" ? void 0 : trimmed.slice(0, LIMITS[key]);
45987
+ };
45988
+ const goal = read("goal");
45989
+ const actual = read("actual");
45990
+ const missing = [goal === void 0 ? "goal" : "", actual === void 0 ? "actual" : ""].filter(
45991
+ (k) => k !== ""
45992
+ );
45993
+ if (goal === void 0 || actual === void 0) {
45994
+ return { error: `A report needs ${missing.join(" and ")}.` };
45995
+ }
45996
+ return { report: { goal, actual, expected: read("expected"), tried: read("tried"), wanted: read("wanted") } };
45997
+ }
45998
+ function appIdOf(projectDir) {
45999
+ try {
46000
+ const raw = fs6.readFileSync(path7.join(projectDir, "package.json"), "utf-8");
46001
+ const pkg2 = JSON.parse(raw);
46002
+ const appId = pkg2.lotics?.app_id;
46003
+ return typeof appId === "string" && appId !== "" ? appId : void 0;
46004
+ } catch {
46005
+ return void 0;
46006
+ }
46007
+ }
46008
+ function echoReport(report) {
46009
+ console.error("\nNothing was sent. Your report, so it is not lost:\n");
46010
+ console.error(JSON.stringify(report, null, 2));
46011
+ }
46012
+ async function reportCommand(client, options) {
46013
+ const report = options.report;
46014
+ const session = getInvocation()?.session ?? null;
46015
+ try {
46016
+ await client.sendReport({
46017
+ report,
46018
+ cli_session_id: session,
46019
+ cli_version: options.version,
46020
+ workspace_id: options.workspaceId,
46021
+ app_id: appIdOf(options.projectDir ?? process.cwd())
46022
+ });
46023
+ } catch (error52) {
46024
+ console.error(
46025
+ `Could not file the report: ${error52 instanceof Error ? error52.message : String(error52)}`
46026
+ );
46027
+ echoReport(report);
46028
+ process.exit(1);
46029
+ }
46030
+ console.error("Filed. Thank you \u2014 this is read.");
46031
+ if (session === null) {
46032
+ console.error(
46033
+ "\n Sent without the session's commands attached \u2014 telemetry is off, so they were\n never recorded. `export LOTICS_TELEMETRY=1` attaches them to the next report,\n which is the difference between a description and a reproduction."
46034
+ );
46035
+ }
46036
+ }
46037
+
46038
+ // src/app_commands.ts
46039
+ import fs8 from "node:fs";
46040
+ import path9 from "node:path";
45620
46041
  import { spawn as spawn2 } from "node:child_process";
45621
46042
  import { randomUUID } from "node:crypto";
45622
46043
  import { tmpdir } from "node:os";
@@ -46298,7 +46719,7 @@ import { Text } from "@lotics/ui/text";
46298
46719
 
46299
46720
  @lotics/ui ships React Native primitives that render via react-native-web
46300
46721
  in this Vite app (the alias is preconfigured in \`vite.config.ts\`). See
46301
- the full export list at https://www.npmjs.com/package/@lotics/ui.
46722
+ the full catalog with \`lotics docs ui\`.
46302
46723
 
46303
46724
  ## Routing
46304
46725
 
@@ -46311,7 +46732,9 @@ shareable and survives a full refresh. Standalone (\`<slug>.lotics.app\`) it's a
46311
46732
  normal browser router with real path URLs. A single-screen app can drop the
46312
46733
  router and render one screen directly.
46313
46734
 
46314
- See https://lotics.ai/docs/app-sdk for the SDK reference.
46735
+ Run \`lotics docs\` for the SDK reference \u2014 it lists what the packages installed
46736
+ here actually ship, at the versions installed, so it can never point at a page
46737
+ that moved. (\`lotics docs ai\`, \`lotics docs queries\`, \`lotics docs ui\`.)
46315
46738
  `
46316
46739
  }
46317
46740
  ];
@@ -47876,8 +48299,8 @@ export type AppRoles = typeof ROLE;
47876
48299
  }
47877
48300
 
47878
48301
  // src/app_workflow_check.ts
47879
- import fs5 from "node:fs";
47880
- import path6 from "node:path";
48302
+ import fs7 from "node:fs";
48303
+ import path8 from "node:path";
47881
48304
  import { createRequire } from "node:module";
47882
48305
 
47883
48306
  // ../shared/src/parse_workflow_js.ts
@@ -48649,10 +49072,10 @@ function mergeDefs(...defs) {
48649
49072
  function cloneDef(schema) {
48650
49073
  return mergeDefs(schema._zod.def);
48651
49074
  }
48652
- function getElementAtPath(obj, path10) {
48653
- if (!path10)
49075
+ function getElementAtPath(obj, path12) {
49076
+ if (!path12)
48654
49077
  return obj;
48655
- return path10.reduce((acc, key) => acc?.[key], obj);
49078
+ return path12.reduce((acc, key) => acc?.[key], obj);
48656
49079
  }
48657
49080
  function promiseAllObject(promisesObj) {
48658
49081
  const keys3 = Object.keys(promisesObj);
@@ -49061,11 +49484,11 @@ function explicitlyAborted(x2, startIndex = 0) {
49061
49484
  }
49062
49485
  return false;
49063
49486
  }
49064
- function prefixIssues(path10, issues) {
49487
+ function prefixIssues(path12, issues) {
49065
49488
  return issues.map((iss) => {
49066
49489
  var _a4;
49067
49490
  (_a4 = iss).path ?? (_a4.path = []);
49068
- iss.path.unshift(path10);
49491
+ iss.path.unshift(path12);
49069
49492
  return iss;
49070
49493
  });
49071
49494
  }
@@ -49212,16 +49635,16 @@ function flattenError(error52, mapper = (issue2) => issue2.message) {
49212
49635
  }
49213
49636
  function formatError(error52, mapper = (issue2) => issue2.message) {
49214
49637
  const fieldErrors = { _errors: [] };
49215
- const processError = (error53, path10 = []) => {
49638
+ const processError = (error53, path12 = []) => {
49216
49639
  for (const issue2 of error53.issues) {
49217
49640
  if (issue2.code === "invalid_union" && issue2.errors.length) {
49218
- issue2.errors.map((issues) => processError({ issues }, [...path10, ...issue2.path]));
49641
+ issue2.errors.map((issues) => processError({ issues }, [...path12, ...issue2.path]));
49219
49642
  } else if (issue2.code === "invalid_key") {
49220
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
49643
+ processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
49221
49644
  } else if (issue2.code === "invalid_element") {
49222
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
49645
+ processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
49223
49646
  } else {
49224
- const fullpath = [...path10, ...issue2.path];
49647
+ const fullpath = [...path12, ...issue2.path];
49225
49648
  if (fullpath.length === 0) {
49226
49649
  fieldErrors._errors.push(mapper(issue2));
49227
49650
  } else {
@@ -49248,17 +49671,17 @@ function formatError(error52, mapper = (issue2) => issue2.message) {
49248
49671
  }
49249
49672
  function treeifyError(error52, mapper = (issue2) => issue2.message) {
49250
49673
  const result = { errors: [] };
49251
- const processError = (error53, path10 = []) => {
49674
+ const processError = (error53, path12 = []) => {
49252
49675
  var _a4, _b2;
49253
49676
  for (const issue2 of error53.issues) {
49254
49677
  if (issue2.code === "invalid_union" && issue2.errors.length) {
49255
- issue2.errors.map((issues) => processError({ issues }, [...path10, ...issue2.path]));
49678
+ issue2.errors.map((issues) => processError({ issues }, [...path12, ...issue2.path]));
49256
49679
  } else if (issue2.code === "invalid_key") {
49257
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
49680
+ processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
49258
49681
  } else if (issue2.code === "invalid_element") {
49259
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
49682
+ processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
49260
49683
  } else {
49261
- const fullpath = [...path10, ...issue2.path];
49684
+ const fullpath = [...path12, ...issue2.path];
49262
49685
  if (fullpath.length === 0) {
49263
49686
  result.errors.push(mapper(issue2));
49264
49687
  continue;
@@ -49290,8 +49713,8 @@ function treeifyError(error52, mapper = (issue2) => issue2.message) {
49290
49713
  }
49291
49714
  function toDotPath(_path) {
49292
49715
  const segs = [];
49293
- const path10 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
49294
- for (const seg of path10) {
49716
+ const path12 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
49717
+ for (const seg of path12) {
49295
49718
  if (typeof seg === "number")
49296
49719
  segs.push(`[${seg}]`);
49297
49720
  else if (typeof seg === "symbol")
@@ -61983,13 +62406,13 @@ function resolveRef(ref2, ctx) {
61983
62406
  if (!ref2.startsWith("#")) {
61984
62407
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
61985
62408
  }
61986
- const path10 = ref2.slice(1).split("/").filter(Boolean);
61987
- if (path10.length === 0) {
62409
+ const path12 = ref2.slice(1).split("/").filter(Boolean);
62410
+ if (path12.length === 0) {
61988
62411
  return ctx.rootSchema;
61989
62412
  }
61990
62413
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
61991
- if (path10[0] === defsKey) {
61992
- const key = path10[1];
62414
+ if (path12[0] === defsKey) {
62415
+ const key = path12[1];
61993
62416
  if (!key || !ctx.defs[key]) {
61994
62417
  throw new Error(`Reference not found: ${ref2}`);
61995
62418
  }
@@ -66672,9 +67095,9 @@ __export(expression_object_exports, {
66672
67095
  pick: () => pick3,
66673
67096
  values: () => values2
66674
67097
  });
66675
- function getNestedValue(obj, path10) {
67098
+ function getNestedValue(obj, path12) {
66676
67099
  if (obj == null) return void 0;
66677
- const parts = path10.split(".");
67100
+ const parts = path12.split(".");
66678
67101
  let value2 = obj;
66679
67102
  for (const part of parts) {
66680
67103
  if (value2 == null) return void 0;
@@ -66713,11 +67136,11 @@ function entries(obj) {
66713
67136
  }
66714
67137
  return Object.entries(obj);
66715
67138
  }
66716
- function get(obj, path10, defaultValue) {
66717
- if (typeof path10 !== "string") {
67139
+ function get(obj, path12, defaultValue) {
67140
+ if (typeof path12 !== "string") {
66718
67141
  throw new Error("get: path must be a string");
66719
67142
  }
66720
- const value2 = getNestedValue(obj, path10);
67143
+ const value2 = getNestedValue(obj, path12);
66721
67144
  return value2 !== void 0 ? value2 : defaultValue;
66722
67145
  }
66723
67146
  function pick3(obj, fields) {
@@ -68600,17 +69023,17 @@ function readFromChanges(chain2) {
68600
69023
  if (first3.kind === "bracket-number" || first3.kind === "bracket-expr") {
68601
69024
  fail(first3.node, "changes is keyed by field name, not numeric index.");
68602
69025
  }
68603
- const path10 = [{ at: "key", key: first3.key }];
69026
+ const path12 = [{ at: "key", key: first3.key }];
68604
69027
  for (const seg of segments.slice(1)) {
68605
69028
  if (seg.kind === "bracket-number") {
68606
- path10.push({ at: "index", index: seg.index });
69029
+ path12.push({ at: "index", index: seg.index });
68607
69030
  } else if (seg.kind === "bracket-expr") {
68608
- path10.push({ at: "index", index: seg.expr });
69031
+ path12.push({ at: "index", index: seg.expr });
68609
69032
  } else {
68610
- path10.push({ at: "key", key: seg.key });
69033
+ path12.push({ at: "key", key: seg.key });
68611
69034
  }
68612
69035
  }
68613
- return { kind: "read", source: { from: "trigger_changes" }, path: path10 };
69036
+ return { kind: "read", source: { from: "trigger_changes" }, path: path12 };
68614
69037
  });
68615
69038
  }
68616
69039
  var TRIGGER_RECORD_VIEWS = /* @__PURE__ */ new Map([
@@ -68628,17 +69051,17 @@ function readFromTrigger(chain2) {
68628
69051
  return view === "trigger_changes" ? readFromChanges(lowered) : readFromRecord(view, lowered);
68629
69052
  }
68630
69053
  return wrapOptionalGuards(chain2, (segments) => {
68631
- const path10 = [];
69054
+ const path12 = [];
68632
69055
  for (const seg of segments) {
68633
69056
  if (seg.kind === "bracket-number") {
68634
- path10.push({ at: "index", index: seg.index });
69057
+ path12.push({ at: "index", index: seg.index });
68635
69058
  } else if (seg.kind === "bracket-expr") {
68636
- path10.push({ at: "index", index: seg.expr });
69059
+ path12.push({ at: "index", index: seg.expr });
68637
69060
  } else {
68638
- path10.push({ at: "key", key: seg.key });
69061
+ path12.push({ at: "key", key: seg.key });
68639
69062
  }
68640
69063
  }
68641
- return { kind: "read", source: { from: "trigger" }, path: path10 };
69064
+ return { kind: "read", source: { from: "trigger" }, path: path12 };
68642
69065
  });
68643
69066
  }
68644
69067
  function readFromRuntime(chain2) {
@@ -68660,17 +69083,17 @@ function readFromRuntime(chain2) {
68660
69083
  const parked = PARKED_RUNTIME_KEYS.get(key);
68661
69084
  if (parked) fail(first3.node, parked, parkedRuntimeKeyConstruct(key));
68662
69085
  const source = { from: "runtime", key };
68663
- const path10 = [];
69086
+ const path12 = [];
68664
69087
  for (const seg of segments.slice(1)) {
68665
69088
  if (seg.kind === "bracket-number") {
68666
- path10.push({ at: "index", index: seg.index });
69089
+ path12.push({ at: "index", index: seg.index });
68667
69090
  } else if (seg.kind === "bracket-expr") {
68668
- path10.push({ at: "index", index: seg.expr });
69091
+ path12.push({ at: "index", index: seg.expr });
68669
69092
  } else {
68670
- path10.push({ at: "key", key: seg.key });
69093
+ path12.push({ at: "key", key: seg.key });
68671
69094
  }
68672
69095
  }
68673
- return { kind: "read", source, path: path10 };
69096
+ return { kind: "read", source, path: path12 };
68674
69097
  });
68675
69098
  }
68676
69099
  function readFromForeachItem(chain2) {
@@ -68706,17 +69129,17 @@ function readFromLexical(name2, chain2) {
68706
69129
  }
68707
69130
  function readFromStepOutput(stepId, chain2) {
68708
69131
  return wrapOptionalGuards(chain2, (segments) => {
68709
- const path10 = [];
69132
+ const path12 = [];
68710
69133
  for (const seg of segments) {
68711
69134
  if (seg.kind === "bracket-number") {
68712
- path10.push({ at: "index", index: seg.index });
69135
+ path12.push({ at: "index", index: seg.index });
68713
69136
  } else if (seg.kind === "bracket-expr") {
68714
- path10.push({ at: "index", index: seg.expr });
69137
+ path12.push({ at: "index", index: seg.expr });
68715
69138
  } else {
68716
- path10.push({ at: "key", key: seg.key });
69139
+ path12.push({ at: "key", key: seg.key });
68717
69140
  }
68718
69141
  }
68719
- return { kind: "read", source: { from: "step_output", step_id: stepId }, path: path10 };
69142
+ return { kind: "read", source: { from: "step_output", step_id: stepId }, path: path12 };
68720
69143
  });
68721
69144
  }
68722
69145
  function classifyRecordPath(segs) {
@@ -70381,7 +70804,7 @@ function subsetIssues(bodySource, offset) {
70381
70804
  ];
70382
70805
  }
70383
70806
  async function loadProjectTypescript(projectDir) {
70384
- const requireFromProject = createRequire(path6.join(projectDir, "package.json"));
70807
+ const requireFromProject = createRequire(path8.join(projectDir, "package.json"));
70385
70808
  let resolved;
70386
70809
  try {
70387
70810
  resolved = requireFromProject.resolve("typescript");
@@ -70394,12 +70817,12 @@ async function loadProjectTypescript(projectDir) {
70394
70817
  return mod2.default ?? mod2;
70395
70818
  }
70396
70819
  function pathToImportUrl(p) {
70397
- const resolved = path6.resolve(p);
70820
+ const resolved = path8.resolve(p);
70398
70821
  const prefixed = resolved.startsWith("/") ? resolved : `/${resolved}`;
70399
- return `file://${prefixed.split(path6.sep).join("/")}`;
70822
+ return `file://${prefixed.split(path8.sep).join("/")}`;
70400
70823
  }
70401
70824
  function checkOneWorkflowBody(tsApi, input) {
70402
- const wrapped = fs5.readFileSync(input.bodyPath, "utf-8");
70825
+ const wrapped = fs7.readFileSync(input.bodyPath, "utf-8");
70403
70826
  const offset = bodyLineOffset(wrapped);
70404
70827
  const subset = subsetIssues(stripWorkflowHeader(wrapped), offset);
70405
70828
  if (subset) return subset;
@@ -70446,7 +70869,7 @@ function checkOneWorkflowBody(tsApi, input) {
70446
70869
  }
70447
70870
  function createCheckedSourceHost(tsApi, options, bodyPath, bodyText) {
70448
70871
  const base = tsApi.createCompilerHost(options, false);
70449
- const key = (fileName) => base.getCanonicalFileName(path6.resolve(fileName).replace(/\\/g, "/"));
70872
+ const key = (fileName) => base.getCanonicalFileName(path8.resolve(fileName).replace(/\\/g, "/"));
70450
70873
  const bodyKey = key(bodyPath);
70451
70874
  return {
70452
70875
  ...base,
@@ -70463,6 +70886,1035 @@ function checkWorkflowBodies(tsApi, aliases) {
70463
70886
  }));
70464
70887
  }
70465
70888
 
70889
+ // ../shared/src/schemas/table_fields.ts
70890
+ var tableFieldTypeNameSchema = zod_default.enum([
70891
+ "text",
70892
+ "number",
70893
+ "date",
70894
+ "boolean",
70895
+ "select",
70896
+ "select_member",
70897
+ "select_record_link",
70898
+ "files",
70899
+ "formula",
70900
+ "rollup",
70901
+ "lookup",
70902
+ "button",
70903
+ "autonumber"
70904
+ ]);
70905
+ var tableFieldBaseSchema = zod_default.object({
70906
+ key: zod_default.string().describe("Unique field key"),
70907
+ name: zod_default.string().describe("Display name"),
70908
+ description: zod_default.string().describe("Field description"),
70909
+ confirm_before_update: zod_default.boolean().optional().describe("Show confirmation before AI updates")
70910
+ });
70911
+ var numberFormatSchema = zod_default.enum(["number", "currency", "percentage"]);
70912
+ var textFormatSchema = zod_default.enum(["text", "link", "markdown"]);
70913
+ var dateFormatSchema = zod_default.enum(["date", "datetime", "date_range", "datetime_range"]);
70914
+ var DEFAULT_VALUE_DESC = "Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. null clears it.";
70915
+ var textDefaultValueSchema = zod_default.string().nullish().describe(DEFAULT_VALUE_DESC);
70916
+ var numberDefaultValueSchema = zod_default.number().nullish().describe(DEFAULT_VALUE_DESC);
70917
+ var booleanDefaultValueSchema = zod_default.boolean().nullish().describe(DEFAULT_VALUE_DESC);
70918
+ var dateDefaultValueSchema = zod_default.string().nullish().describe(`${DEFAULT_VALUE_DESC} A date string in the field's format.`);
70919
+ var selectDefaultValueSchema = zod_default.array(zod_default.string()).nullish().describe(`${DEFAULT_VALUE_DESC} Option key(s) \u2014 one for single-select.`);
70920
+ var memberDefaultValueSchema = zod_default.array(zod_default.string()).nullish().describe(`${DEFAULT_VALUE_DESC} Member ID(s) \u2014 one for single-select.`);
70921
+ var tableNumberFieldSchema = tableFieldBaseSchema.extend({
70922
+ type: zod_default.literal("number"),
70923
+ format: numberFormatSchema,
70924
+ currency: zod_default.string().optional().describe("ISO 4217 code"),
70925
+ default_value: numberDefaultValueSchema
70926
+ });
70927
+ var tableTextFieldSchema = tableFieldBaseSchema.extend({
70928
+ type: zod_default.literal("text"),
70929
+ unique: zod_default.boolean().optional().describe("Unique values required"),
70930
+ // it's `optional` only for backward compatibility
70931
+ format: textFormatSchema.optional(),
70932
+ default_value: textDefaultValueSchema
70933
+ });
70934
+ var tableDateFieldSchema = tableFieldBaseSchema.extend({
70935
+ type: zod_default.literal("date"),
70936
+ format: dateFormatSchema.optional(),
70937
+ timezone: zod_default.string().optional().describe("IANA timezone"),
70938
+ derive_from: zod_default.enum(["created_at", "updated_at"]).optional().describe(
70939
+ "Auto-populate from the row's system timestamp. `created_at` stamps once at insert time; `updated_at` re-stamps on every record edit. Derived fields are read-only \u2014 user-supplied values are silently ignored at write time, matching the formula/rollup/lookup pattern. Replaces the per-table after_create workflow pattern."
70940
+ ),
70941
+ // A derived date is read-only, so derive_from and default_value are mutually
70942
+ // exclusive — enforced in validateFieldDefaultValue.
70943
+ default_value: dateDefaultValueSchema
70944
+ });
70945
+ var tableBooleanFieldSchema = tableFieldBaseSchema.extend({
70946
+ type: zod_default.literal("boolean"),
70947
+ default_value: booleanDefaultValueSchema
70948
+ });
70949
+ var tableSelectFieldOptionSchema = zod_default.object({
70950
+ key: zod_default.string(),
70951
+ name: zod_default.string(),
70952
+ color: optionColorSchema
70953
+ });
70954
+ var tableSelectFieldSchema = tableFieldBaseSchema.extend({
70955
+ type: zod_default.literal("select"),
70956
+ options: zod_default.array(tableSelectFieldOptionSchema),
70957
+ multi: zod_default.boolean().optional().describe("Allow multiple selections"),
70958
+ default_value: selectDefaultValueSchema
70959
+ });
70960
+ var tableSelectMemberFieldSchema = tableFieldBaseSchema.extend({
70961
+ type: zod_default.literal("select_member"),
70962
+ multi: zod_default.boolean().optional().describe("Allow multiple selections"),
70963
+ default_value: memberDefaultValueSchema
70964
+ });
70965
+ var recordLinkItemSchema = zod_default.object({
70966
+ id: zod_default.string(),
70967
+ display: zod_default.string()
70968
+ });
70969
+ var selectRecordLinkValueSchema = zod_default.array(recordLinkItemSchema);
70970
+ var tableSelectRecordLinkFieldInputSchema = tableFieldBaseSchema.extend({
70971
+ type: zod_default.literal("select_record_link"),
70972
+ table_id: zod_default.string().describe("Linked table ID"),
70973
+ display_field_keys: zod_default.array(zod_default.string()).optional().describe(
70974
+ "Field keys of the linked table shown as the link's display text and as the columns in the record picker. If omitted, auto-selects the first text field."
70975
+ ),
70976
+ display_field_widths: zod_default.record(zod_default.string(), zod_default.number().int().positive()).optional().describe(
70977
+ "Per-column widths in pixels for the record picker, keyed by linked-table field key. Missing keys fall back to the default column width."
70978
+ ),
70979
+ sync_both_ways: zod_default.boolean().optional().describe("Enable bidirectional link sync. If the target table has exactly one existing unpaired link field pointing back, pairs with it instead of creating a new field."),
70980
+ paired_field_display_field_keys: zod_default.array(zod_default.string()).optional().describe("Display fields for paired link"),
70981
+ paired_field_name: zod_default.string().optional().describe(
70982
+ "Name for the auto-created paired field on the target table when sync_both_ways=true. If omitted, falls back to the source table's name (auto-uniquified with a numeric suffix on collision). Rejected as a conflict if a field with this name already exists on the target."
70983
+ ),
70984
+ cardinality: zod_default.enum(["one", "many"]).optional().describe(
70985
+ "How many linked records this field can hold. Default 'many' (multi-select). 'one' = single-select; this side holds exactly one link to the partner. Requires sync_both_ways/paired_field_key. Setting 'one' on both paired sides is rejected."
70986
+ )
70987
+ });
70988
+ var tableSelectRecordLinkFieldSchema = tableFieldBaseSchema.extend({
70989
+ type: zod_default.literal("select_record_link").describe("Field type for linking to records in another table"),
70990
+ table_id: zod_default.string().describe("ID of the target table that records can be linked to"),
70991
+ display_field_keys: zod_default.array(zod_default.string()).optional().default([]).describe(
70992
+ "Field keys of the linked table shown as the link's display text and as the columns in the record picker. If empty, the picker shows all fields."
70993
+ ),
70994
+ display_field_widths: zod_default.record(zod_default.string(), zod_default.number().int().positive()).optional().describe(
70995
+ "Per-column widths in pixels for the record picker, keyed by linked-table field key. Missing keys fall back to the default column width."
70996
+ ),
70997
+ paired_field_key: zod_default.string().optional().describe(
70998
+ "Key of the paired link field in the linked table. If set to a string, this indicates a bidirectional link where both tables can reference each other and data synchronizes in both directions. Both fields have paired_field_key pointing to each other. If set to null, explicitly disconnects the bidirectional link (clears paired_field_key on both sides). If undefined, no change is made to the existing paired_field_key."
70999
+ ),
71000
+ cardinality: zod_default.enum(["one", "many"]).optional().describe(
71001
+ "How many linked records this field can hold. Default 'many' (multi-select). 'one' = single-select; this side holds exactly one link to the partner. Requires paired_field_key. Two paired sides cannot both be 'one'."
71002
+ )
71003
+ });
71004
+ var tableRollupFieldSchema = tableFieldBaseSchema.extend({
71005
+ type: zod_default.literal("rollup").describe(
71006
+ "Field type for aggregating values from linked records (sum, avg, filled, earliest, etc.)"
71007
+ ),
71008
+ source_field_key: zod_default.string().describe("The field key of the select_record_link field to rollup from"),
71009
+ aggregate_option: aggregateOptionSchema.describe(
71010
+ "The field aggregation function to apply (e.g., sum, avg, filled, earliest)"
71011
+ ),
71012
+ filter: tableRecordFiltersGroupNodeSchema.optional().describe(
71013
+ "Optional filter to apply to linked records before aggregation. Only records matching the filter conditions will be included in the rollup calculation."
71014
+ ),
71015
+ aggregate_field_type: tableFieldTypeNameSchema.optional().describe(
71016
+ "What this rollup's CELL holds \u2014 'date' for earliest/latest, 'number' for every other operation (counts, sums, percentages). Derived from the operation, not from the aggregated field: a 'filled' count over a files column is a number. Server-derived at write time; ignored on input."
71017
+ ),
71018
+ aggregate_field_format: zod_default.string().optional().describe(
71019
+ "The format of this rollup's own value. Operations that preserve the aggregated value's unit (sum/avg/median/min/max/range/earliest/latest) inherit the target field's format; counting operations are 'number', percentages 'percentage'. Server-derived at write time; ignored on input."
71020
+ ),
71021
+ aggregate_field_currency: zod_default.string().optional().describe(
71022
+ "The currency code of this rollup's own value (e.g. 'USD', 'VND'), inherited from the aggregated field only when the operation preserves its unit \u2014 a COUNT of a currency column carries no currency. Server-derived at write time; ignored on input."
71023
+ )
71024
+ });
71025
+ var lookupOrderBySchema = zod_default.object({
71026
+ field_key: zod_default.string(),
71027
+ direction: zod_default.enum(["asc", "desc"])
71028
+ });
71029
+ var tableLookupFieldSchema = tableFieldBaseSchema.extend({
71030
+ type: zod_default.literal("lookup").describe("Field type for displaying values from fields in linked records"),
71031
+ source_field_key: zod_default.string().describe("The field key of the select_record_link field to lookup from"),
71032
+ lookup_field_key: zod_default.string().describe("The field key from the linked records to display"),
71033
+ lookup_field_type: tableFieldTypeNameSchema.optional().describe(
71034
+ "The type of the target lookup field (e.g. 'text', 'number', 'date'). Resolved from the linked table's field definition at read time."
71035
+ ),
71036
+ lookup_field_format: zod_default.string().optional().describe(
71037
+ "The format of the target lookup field (e.g. 'currency', 'percentage', 'datetime'). Resolved from the linked table's field definition at read time."
71038
+ ),
71039
+ lookup_field_currency: zod_default.string().optional().describe(
71040
+ "The currency code of the target lookup field (e.g. 'USD', 'VND'). Resolved from the linked table's field definition at read time."
71041
+ ),
71042
+ lookup_field_options: zod_default.array(zod_default.object({ key: zod_default.string(), name: zod_default.string(), color: zod_default.string().optional() })).optional().describe(
71043
+ "Options for select-type lookup fields. Synced from the source select field's options."
71044
+ ),
71045
+ order_by: lookupOrderBySchema.optional().describe(
71046
+ "When set, sort the linked records by this field on the linked table and return ONLY the picked field from the single extreme record (desc=latest, asc=earliest), as a one-element array. Omit to return every linked record's value as an array. field_key must be a field on the linked table."
71047
+ )
71048
+ });
71049
+ var tableFilesFieldSchema = tableFieldBaseSchema.extend({
71050
+ type: zod_default.literal("files").describe("Field type for file attachments (images, PDFs, documents)")
71051
+ });
71052
+ var formulaInputSchema = zod_default.object({
71053
+ expression: zod_default.string().describe(
71054
+ "JavaScript expression. Uses {Field Name} for field references."
71055
+ ),
71056
+ format: zod_default.enum(["number", "currency", "percentage", "link"]).optional().describe(
71057
+ "Display format. 'number' / 'currency' / 'percentage' for numeric results; 'link' for text-output formulas that return a URL \u2014 renders the result as a clickable link."
71058
+ ),
71059
+ currency: zod_default.string().optional().describe("ISO 4217 currency code, e.g. USD, VND, EUR")
71060
+ });
71061
+ var formulaSchema = formulaInputSchema.extend({
71062
+ output_type: zod_default.enum(["number", "text", "date", "datetime", "boolean"]).optional().describe("Output type of the formula. Inferred by the backend from the expression \u2014 read-only on input. `datetime` when the result carries a time of day (references a datetime field or a time-bearing helper)."),
71063
+ volatile: zod_default.boolean().optional().describe("Whether the formula's result depends on `now()` or other non-deterministic helpers. Inferred by the backend \u2014 read-only on input.")
71064
+ });
71065
+ var tableFormulaFieldSchema = tableFieldBaseSchema.extend({
71066
+ type: zod_default.literal("formula").describe("Field type for calculated values based on expressions"),
71067
+ formula: formulaSchema.describe("Formula configuration and expression")
71068
+ });
71069
+ var workflowMetadataSchema = zod_default.object({
71070
+ table_ids: zod_default.array(zod_default.string()).describe("IDs of the tables this workflow affects"),
71071
+ staged_for_app_id: zod_default.string().optional().describe(
71072
+ "Present while the row is a draft-staged fork for this app; cleared when the app publishes the binding. Marked rows left unbound are archived at publish/discard."
71073
+ )
71074
+ });
71075
+ var workflowSchema = zod_default.object({
71076
+ id: zod_default.string().describe("Unique identifier for the workflow"),
71077
+ name: zod_default.string().describe("Display name of the workflow"),
71078
+ description: zod_default.string().describe("Human-readable description of what this workflow does"),
71079
+ steps: zod_default.unknown().describe(
71080
+ "AST-valued step tree (tool calls, conditionals, loops)."
71081
+ ),
71082
+ metadata: workflowMetadataSchema.nullable().optional().describe("Metadata for the workflow"),
71083
+ current_version: zod_default.number().int().describe("Current version number"),
71084
+ enabled: zod_default.boolean().describe("Whether this workflow is active"),
71085
+ workspace_id: zod_default.string().describe("ID of the workspace this workflow belongs to"),
71086
+ created_at: zod_default.string().describe("Timestamp when workflow was created"),
71087
+ updated_at: zod_default.string().describe("Timestamp of last update")
71088
+ });
71089
+ var tableButtonFieldSchema = tableFieldBaseSchema.extend({
71090
+ type: zod_default.literal("button").describe("Field type for action buttons"),
71091
+ text: zod_default.string().describe("Button label text displayed to users"),
71092
+ workflow_id: zod_default.string().nullable().optional().describe(
71093
+ "ID of the workflow the button presses trigger. Null means no action configured \u2014 pressing the button is a no-op. Button authoring is retired: existing bindings keep running, but nothing sets a new one."
71094
+ )
71095
+ });
71096
+ var tableAutonumberFieldSchema = tableFieldBaseSchema.extend({
71097
+ type: zod_default.literal("autonumber"),
71098
+ prefix: zod_default.string().optional().describe(
71099
+ "Literal prefix prepended to every display value (e.g. 'KH-' \u2192 'KH-001'). Ignored when `template` is set."
71100
+ ),
71101
+ padding: zod_default.number().int().min(1).max(20).optional().describe(
71102
+ "Zero-pad the integer to this width. Default 1 (no padding). 3 \u2192 '001', '012', '123', '1234' (overflow uses the actual width). Ignored when `template` is set."
71103
+ ),
71104
+ template: zod_default.string().optional().describe(
71105
+ "Format template with placeholder tokens evaluated at insert time. Tokens: {N} (raw integer), {N:W} (zero-padded to width W, e.g. {N:3} \u2192 001), {YEAR} (4-digit year), {YEAR:2} (2-digit year), {MONTH} (2-digit month), {DAY} (2-digit day). Date tokens use the workspace timezone. Example: 'HM-{YEAR}-{N:3}' yields 'HM-2026-001'. Stored as the composed string; subsequent template edits do NOT re-format existing rows (date tokens would lose the original creation date)."
71106
+ )
71107
+ });
71108
+ var tableFieldSchema = zod_default.discriminatedUnion("type", [
71109
+ tableNumberFieldSchema,
71110
+ tableTextFieldSchema,
71111
+ tableDateFieldSchema,
71112
+ tableBooleanFieldSchema,
71113
+ tableSelectFieldSchema,
71114
+ tableSelectMemberFieldSchema,
71115
+ tableSelectRecordLinkFieldSchema,
71116
+ tableRollupFieldSchema,
71117
+ tableLookupFieldSchema,
71118
+ tableFilesFieldSchema,
71119
+ tableFormulaFieldSchema,
71120
+ tableButtonFieldSchema,
71121
+ tableAutonumberFieldSchema
71122
+ ]);
71123
+ var tableFieldInputSchema = zod_default.discriminatedUnion("type", [
71124
+ tableNumberFieldSchema,
71125
+ tableTextFieldSchema,
71126
+ tableDateFieldSchema,
71127
+ tableBooleanFieldSchema,
71128
+ tableSelectFieldSchema,
71129
+ tableSelectMemberFieldSchema,
71130
+ tableSelectRecordLinkFieldInputSchema,
71131
+ tableRollupFieldSchema,
71132
+ tableLookupFieldSchema,
71133
+ tableFilesFieldSchema,
71134
+ tableFormulaFieldSchema,
71135
+ tableButtonFieldSchema,
71136
+ tableAutonumberFieldSchema
71137
+ ]);
71138
+ var tableFieldsSchema = zod_default.array(tableFieldSchema);
71139
+ var tableFieldCreateBaseSchema = zod_default.object({
71140
+ name: zod_default.string().describe("Display name"),
71141
+ description: zod_default.string().describe("Field description"),
71142
+ confirm_before_update: zod_default.boolean().optional().describe("Show confirmation before AI updates")
71143
+ });
71144
+ var tableSelectFieldOptionCreateSchema = zod_default.object({
71145
+ name: zod_default.string(),
71146
+ color: optionColorSchema
71147
+ });
71148
+ var tableFieldTypeConfigSchema = zod_default.discriminatedUnion("type", [
71149
+ zod_default.object({
71150
+ type: zod_default.literal("number"),
71151
+ format: numberFormatSchema.default("number"),
71152
+ currency: zod_default.string().optional().describe("ISO 4217 code"),
71153
+ default_value: numberDefaultValueSchema
71154
+ }),
71155
+ zod_default.object({
71156
+ type: zod_default.literal("text"),
71157
+ unique: zod_default.boolean().optional().describe("Unique values required"),
71158
+ format: textFormatSchema.default("text"),
71159
+ default_value: textDefaultValueSchema
71160
+ }),
71161
+ zod_default.object({
71162
+ type: zod_default.literal("date"),
71163
+ format: dateFormatSchema.optional(),
71164
+ timezone: zod_default.string().optional().describe("IANA timezone"),
71165
+ derive_from: zod_default.enum(["created_at", "updated_at"]).nullable().optional().describe("Auto-populate from row system timestamp; field becomes read-only. Only valid for date/datetime formats. Pass null to clear and revert to a manual date field."),
71166
+ default_value: dateDefaultValueSchema
71167
+ }),
71168
+ zod_default.object({
71169
+ type: zod_default.literal("boolean"),
71170
+ default_value: booleanDefaultValueSchema
71171
+ }),
71172
+ zod_default.object({
71173
+ type: zod_default.literal("select"),
71174
+ options: zod_default.array(tableSelectFieldOptionCreateSchema),
71175
+ multi: zod_default.boolean().optional().describe("Allow multiple selections"),
71176
+ default_value: selectDefaultValueSchema
71177
+ }),
71178
+ zod_default.object({
71179
+ type: zod_default.literal("select_member"),
71180
+ multi: zod_default.boolean().optional().describe("Allow multiple selections"),
71181
+ default_value: memberDefaultValueSchema
71182
+ }),
71183
+ zod_default.object({
71184
+ type: zod_default.literal("select_record_link"),
71185
+ table_id: zod_default.string().describe("Linked table ID"),
71186
+ display_field_keys: zod_default.array(zod_default.string()).optional().describe(
71187
+ "Field keys of the linked table shown as the link's display text and as the columns in the record picker. If omitted, auto-selects the first text field."
71188
+ ),
71189
+ display_field_widths: zod_default.record(zod_default.string(), zod_default.number().int().positive()).optional().describe(
71190
+ "Per-column widths in pixels for the record picker, keyed by linked-table field key. Missing keys fall back to the default column width."
71191
+ ),
71192
+ sync_both_ways: zod_default.boolean().optional().describe("Enable bidirectional link sync. If the target table has exactly one existing unpaired link field pointing back, pairs with it instead of creating a new field."),
71193
+ paired_field_display_field_keys: zod_default.array(zod_default.string()).optional().describe("Paired field display"),
71194
+ paired_field_name: zod_default.string().optional().describe(
71195
+ "Name for the auto-created paired field on the target table when sync_both_ways=true. Omit to default to the source table's name (auto-uniquified on collision). If supplied and a field with the same name already exists on the target, the create is rejected."
71196
+ ),
71197
+ cardinality: zod_default.enum(["one", "many"]).optional().describe(
71198
+ "How many linked records this field can hold. Default 'many' (multi-select). 'one' = single-select; this side holds exactly one link to the partner. Requires sync_both_ways. Two paired sides cannot both be 'one'."
71199
+ )
71200
+ }),
71201
+ zod_default.object({
71202
+ type: zod_default.literal("rollup"),
71203
+ source_field_key: zod_default.string().describe("Source link field key"),
71204
+ aggregate_option: aggregateOptionSchema,
71205
+ filter: tableRecordFiltersGroupNodeSchema.optional().describe("Filter linked records")
71206
+ }),
71207
+ zod_default.object({
71208
+ type: zod_default.literal("lookup"),
71209
+ source_field_key: zod_default.string().describe("Source link field key"),
71210
+ lookup_field_key: zod_default.string().describe("Field to display"),
71211
+ order_by: lookupOrderBySchema.optional().describe("Ordered lookup: pick only the extreme linked record's field (desc=latest, asc=earliest)")
71212
+ }),
71213
+ zod_default.object({
71214
+ type: zod_default.literal("files")
71215
+ }),
71216
+ zod_default.object({
71217
+ type: zod_default.literal("formula"),
71218
+ formula: formulaInputSchema
71219
+ }),
71220
+ // `button` is deliberately absent: button fields are legacy — this union
71221
+ // shapes CREATE input only, and new buttons can no longer be created
71222
+ // (see LEGACY_FIELD_TYPES). Stored button fields still parse via the
71223
+ // stored-field union above, and update paths keep accepting text edits.
71224
+ zod_default.object({
71225
+ type: zod_default.literal("autonumber"),
71226
+ prefix: zod_default.string().optional().describe("Literal prefix prepended to display (ignored when template is set)"),
71227
+ padding: zod_default.number().int().min(1).max(20).optional().describe("Zero-pad the integer to this width (default 1) (ignored when template is set)"),
71228
+ template: zod_default.string().optional().describe(
71229
+ "Format template \u2014 tokens evaluate at insert time: {N}, {N:W} (padded), {YEAR}, {YEAR:2}, {MONTH}, {DAY}. Date tokens use workspace timezone. Example: 'HM-{YEAR}-{N:3}' \u2192 'HM-2026-001'."
71230
+ )
71231
+ })
71232
+ ]);
71233
+ var tableFieldCreateInputSchema = zod_default.intersection(
71234
+ tableFieldCreateBaseSchema,
71235
+ tableFieldTypeConfigSchema
71236
+ );
71237
+ var fieldVisibilitySchema = zod_default.enum(["visible", "hidden"]).describe(
71238
+ "Field visibility state: 'visible' = shown to everyone, 'hidden' = not shown by default but members can toggle"
71239
+ );
71240
+
71241
+ // ../shared/src/schemas/field_config.ts
71242
+ var fieldConfigEntrySchema = zod_default.object({
71243
+ key: zod_default.string().min(1).describe("Field key"),
71244
+ visibility: fieldVisibilitySchema,
71245
+ width: zod_default.number().optional()
71246
+ });
71247
+
71248
+ // ../shared/src/schemas/iam.ts
71249
+ var jsonSchemaSchema = zod_default.string();
71250
+ var organizationSchema = zod_default.object({
71251
+ id: zod_default.string().describe("Unique identifier for the organization"),
71252
+ name: zod_default.string().describe("Display name of the organization"),
71253
+ slug: zod_default.string().nullable().describe("Slug for the organization")
71254
+ });
71255
+ var memberSchema = zod_default.object({
71256
+ member_id: zod_default.string().describe("Unique identifier for the member"),
71257
+ organization_id: zod_default.string().describe("Organization ID the member belongs to"),
71258
+ name: zod_default.string().describe("Display name of the member"),
71259
+ email_address: zod_default.string().describe("Email address of the member"),
71260
+ role: zod_default.enum(["owner", "admin", "member"]).describe("Organization role"),
71261
+ image: zod_default.string().nullable().optional().describe("URL to the member's profile picture"),
71262
+ account_type: zod_default.enum(["self_managed", "admin_managed"]).describe(
71263
+ "How the account is credentialed. admin_managed means an admin chose the sign-in identifier and set the password, so the address has no inbox \u2014 nothing may be emailed to it until the member claims a real one."
71264
+ )
71265
+ });
71266
+ var pendingInvitationSchema = zod_default.object({
71267
+ invitation_id: zod_default.string().describe("Unique identifier for the pending invitation"),
71268
+ organization_id: zod_default.string().describe("Organization the invitee will join on accept"),
71269
+ email: zod_default.string().describe("Email the invitation was sent to"),
71270
+ name: zod_default.string().nullable().describe("Admin-supplied display name (carried onto member.name on accept; invitee can override at accept time)"),
71271
+ role: zod_default.enum(["owner", "admin", "member"]).describe("Role the invitee will receive on accept"),
71272
+ expires_at: zod_default.string().describe("ISO timestamp when the invitation expires")
71273
+ });
71274
+ var workspaceSchema = zod_default.object({
71275
+ id: zod_default.string().describe("Unique identifier for the workspace"),
71276
+ name: zod_default.string().describe("Display name of the workspace"),
71277
+ default_currency: zod_default.string().describe("Default currency code for the workspace"),
71278
+ timezone: zod_default.string().describe("IANA timezone name (e.g., 'America/New_York', 'Asia/Tokyo')"),
71279
+ organization_id: zod_default.string().describe("Organization ID this workspace belongs to"),
71280
+ created_at: zod_default.string().describe("Timestamp when workspace was created")
71281
+ });
71282
+ var memberGroupSchema = zod_default.object({
71283
+ id: zod_default.string().describe("Unique identifier for the member group"),
71284
+ name: zod_default.string().describe("Display name of the group"),
71285
+ description: zod_default.string().optional().describe("Optional description of the group's purpose"),
71286
+ member_ids: zod_default.array(zod_default.string()).describe("Array of member IDs in this group"),
71287
+ invitation_ids: zod_default.array(zod_default.string()).describe("Array of pending invitation IDs pre-granted membership; migrated to member_ids on accept"),
71288
+ organization_id: zod_default.string().describe("ID of the organization this group belongs to"),
71289
+ created_at: zod_default.string().describe("Timestamp when group was created"),
71290
+ created_by: zod_default.string().describe("Member ID of the user who created this group")
71291
+ });
71292
+ var principalGroupSchema = zod_default.object({
71293
+ id: zod_default.string().describe("Unique identifier for the member group"),
71294
+ name: zod_default.string().describe("Display name of the group"),
71295
+ description: zod_default.string().optional().describe("Optional description of the group's purpose"),
71296
+ member_count: zod_default.number().int().describe("Number of accepted members in the group"),
71297
+ invitation_count: zod_default.number().int().describe("Number of pending invitations pre-granted membership")
71298
+ });
71299
+ var PUBLIC_PRINCIPAL_ID = "public";
71300
+ var principalSchema = zod_default.discriminatedUnion("type", [
71301
+ zod_default.object({
71302
+ type: zod_default.literal("member").describe("Principal type: individual member"),
71303
+ id: zod_default.string().describe("Member ID")
71304
+ }),
71305
+ zod_default.object({
71306
+ type: zod_default.literal("member_group").describe("Principal type: group of members"),
71307
+ id: zod_default.string().describe("Member group ID")
71308
+ }),
71309
+ zod_default.object({
71310
+ type: zod_default.literal("organization").describe("Principal type: all members in organization"),
71311
+ id: zod_default.string().describe("Organization ID")
71312
+ }),
71313
+ zod_default.object({
71314
+ type: zod_default.literal("public").describe("Principal type: anyone, including unauthenticated visitors"),
71315
+ id: zod_default.literal(PUBLIC_PRINCIPAL_ID).describe("Always the `public` sentinel \u2014 no entity")
71316
+ }),
71317
+ // Pending invitation as principal: lets admins pre-grant group membership and
71318
+ // resource access before the invitee accepts. Never resolved as a Subject —
71319
+ // a pending invitee cannot authenticate, so these bindings stay dormant until
71320
+ // accept-time migration rewrites them to type:"member".
71321
+ zod_default.object({
71322
+ type: zod_default.literal("invitation").describe("Principal type: pending invitation (not yet accepted)"),
71323
+ id: zod_default.string().describe("Invitation ID")
71324
+ })
71325
+ ]);
71326
+ var subjectSchema = zod_default.discriminatedUnion("type", [
71327
+ zod_default.object({
71328
+ type: zod_default.literal("member").describe("Subject type: authenticated member"),
71329
+ id: zod_default.string().describe("Member ID")
71330
+ }),
71331
+ zod_default.object({
71332
+ type: zod_default.literal("public").describe("Subject type: unauthenticated visitor")
71333
+ })
71334
+ ]);
71335
+ var resourceTypeSchema = zod_default.enum(["table", "table_view", "app", "automation", "skill", "document_template", "instruction", "knowledge_doc"]);
71336
+ var resourceRoleBindingSchema = zod_default.object({
71337
+ id: zod_default.string().describe("Unique identifier for the role binding"),
71338
+ principal: principalSchema.describe(
71339
+ "Who this role binding applies to (member, group, or organization)"
71340
+ ),
71341
+ resource_type: resourceTypeSchema.describe("Type of resource being accessed"),
71342
+ resource_id: zod_default.string().describe("ID of the specific resource"),
71343
+ role: zod_default.string().describe("Role name (e.g., 'viewer', 'editor', 'manager', 'user')")
71344
+ });
71345
+
71346
+ // ../shared/src/schemas/table_records.ts
71347
+ var tableWorkflowEventSchema = zod_default.enum([
71348
+ "before_create",
71349
+ "after_create",
71350
+ "before_update",
71351
+ "after_update",
71352
+ "before_delete",
71353
+ "after_delete"
71354
+ ]);
71355
+ var tableWorkflowSchema = zod_default.object({
71356
+ id: zod_default.string().describe("Unique table-workflow binding ID (tbw_ prefix)"),
71357
+ name: zod_default.string().describe("Human-readable name"),
71358
+ description: zod_default.string().describe("Description of what this workflow does"),
71359
+ enabled: zod_default.boolean().describe("Whether this workflow is active"),
71360
+ event: tableWorkflowEventSchema.describe(
71361
+ "Lifecycle event that fires this workflow. before_* runs synchronously and may reject the mutation; after_* runs after commit and does not block."
71362
+ ),
71363
+ workflow_id: zod_default.string().describe(
71364
+ "ID of the workflow row that owns this binding's steps. The workflow's steps_v2 is the source of truth for the AST."
71365
+ ),
71366
+ // Recursive v2 expression AST. Lazy schema would crash the OpenAPI walker;
71367
+ // runtime is `unknown` and the casted Zod type preserves the compile-time
71368
+ // signature for callers. Write paths re-parse with the strict schema before
71369
+ // storage.
71370
+ if: zod_default.unknown().nullable().optional().describe(
71371
+ "Optional pre-execution gate. Evaluated against the trigger context (record / prev_record / changes / runtime); the workflow fires only when truthy. Skip gating means the workflow always fires."
71372
+ ),
71373
+ created_at: zod_default.string().describe("When the binding was created"),
71374
+ updated_at: zod_default.string().describe("When the binding was last modified"),
71375
+ archived_at: zod_default.string().nullable().optional().describe(
71376
+ "Set when the binding was removed (soft-delete). An archived binding never fires and is hidden from active listings, but is restorable; null/absent = active."
71377
+ )
71378
+ });
71379
+ var tableWorkflowMessageSchema = zod_default.object({
71380
+ table_workflow_id: zod_default.string().describe("tbw_* binding that emitted the message."),
71381
+ workflow_name: zod_default.string().describe("Human name of the workflow at execution time."),
71382
+ event: tableWorkflowEventSchema.describe(
71383
+ "Lifecycle event that produced the message. Only before_* events can surface here."
71384
+ ),
71385
+ record_id: zod_default.string().describe(
71386
+ "Record that triggered the workflow. Lets the UI scope the message to a specific row in bulk responses."
71387
+ ),
71388
+ message: zod_default.string().describe("Author-provided text from the workflow's return.message.")
71389
+ });
71390
+ var tableSchema = zod_default.object({
71391
+ id: zod_default.string().describe("Unique identifier for the table"),
71392
+ workspace_id: zod_default.string().describe("ID of the workspace this table belongs to"),
71393
+ name: zod_default.string().describe("Display name of the table"),
71394
+ description: zod_default.string().describe("Description of the table's purpose"),
71395
+ position: zod_default.number().int().optional().nullable().describe("Display ordering index for the table. Lower numbers are shown first. "),
71396
+ fields: tableFieldsSchema.describe("Array of field definitions for this table"),
71397
+ workflows: zod_default.array(tableWorkflowSchema).optional().describe(
71398
+ "Workflows attached to this table that fire around record mutations. before_* run synchronously and may reject; after_* run after commit and do not block."
71399
+ ),
71400
+ private_filters: tableRecordFiltersGroupNodeSchema.nullable().optional().describe("Row-level private filters AND'd to every query as a security boundary (admin bypass)"),
71401
+ colors: tableRecordColorsSchema.nullable().optional().describe("Color rules for styling records based on filter conditions"),
71402
+ approvers: zod_default.array(principalSchema).nullable().optional().describe(
71403
+ "Principals (members, groups, or the organization) who review locked-record change requests on this table. Null/empty = organization admins review. The requester never reviews their own request."
71404
+ ),
71405
+ created_at: zod_default.string().describe("Timestamp when table was created")
71406
+ });
71407
+ var recordDataTemplateSchema = zod_default.object({
71408
+ id: zod_default.string().describe("Unique identifier for the record data template"),
71409
+ name: zod_default.string().describe("Display name of the record data template"),
71410
+ description: zod_default.string().describe("Description of the record data template"),
71411
+ table_id: zod_default.string().describe("ID of the table this record template belongs to"),
71412
+ data: zod_default.record(zod_default.string(), zod_default.unknown()).describe("The data of the record template. The key is the Field key.")
71413
+ });
71414
+ var tableRecordSchema = zod_default.object({
71415
+ id: zod_default.string().describe("Unique identifier for the record"),
71416
+ table_id: zod_default.string().describe("ID of the table this record belongs to"),
71417
+ workspace_id: zod_default.string().describe("ID of the workspace this record belongs to"),
71418
+ data: zod_default.record(zod_default.string(), zod_default.unknown()).describe("The data of the record. The key is the Field key."),
71419
+ locked: zod_default.boolean().optional().describe("Whether the entire record is locked from editing"),
71420
+ draft: zod_default.boolean().optional().describe("Whether the record is an unsubmitted draft \u2014 UI-only in-progress state, not yet a real row. Row-visibility scoping is enforced at submit, not while a draft is being filled in."),
71421
+ updated_at: zod_default.string().describe("Timestamp of last update"),
71422
+ created_at: zod_default.string().describe("Timestamp when record was created")
71423
+ });
71424
+ var viewSummaryEntrySchema = zod_default.object({
71425
+ operation: zod_default.string().describe("Operation to perform on the field"),
71426
+ field_key: zod_default.string().describe("Field key to display in the summary row")
71427
+ });
71428
+ var viewSummarySchema = zod_default.record(zod_default.string(), viewSummaryEntrySchema.nullable().optional()).nullable().optional().describe("Map of field keys to summary operations.");
71429
+ var visibleToEntrySchema = zod_default.object({
71430
+ type: zod_default.enum(["member", "group"]).describe("Principal kind that may see the view: an individual member or a member group"),
71431
+ id: zod_default.string().describe("Member ID (type=member) or member-group ID (type=group)")
71432
+ });
71433
+ var visibleToSchema = zod_default.array(visibleToEntrySchema).nullable();
71434
+ var tableViewSchema = zod_default.object({
71435
+ id: zod_default.string().describe("Unique identifier for the view"),
71436
+ table_id: zod_default.string().describe("ID of the table this view displays"),
71437
+ workspace_id: zod_default.string().describe("ID of the workspace this view belongs to"),
71438
+ name: zod_default.string().describe("Display name of the view"),
71439
+ description: zod_default.string().describe("Description of the view's purpose"),
71440
+ visible_to: visibleToSchema.optional().describe(
71441
+ "Sidebar declutter list (NOT access control). null/absent = every member with access to the parent table sees this view; a non-empty list of members/groups restricts who sees it in the explorer. [] is normalized to null on write."
71442
+ ),
71443
+ position: zod_default.number().int().nullable().describe(
71444
+ "Display ordering index for the view. Lower numbers are shown first. Use incremental values with gaps (100, 200, 300) instead of consecutive numbers (1, 2, 3) to enable efficient reordering without updating multiple records. Example: To insert between 100 and 200, use 150."
71445
+ ),
71446
+ frozen_columns: zod_default.number().int().nullable().optional().describe("Number of columns to freeze. Frozen columns are not scrollable."),
71447
+ summary: viewSummarySchema,
71448
+ default_record_data_template_id: zod_default.string().nullable().optional().describe("ID of the record template to use as the default record initial data"),
71449
+ field_config: zod_default.array(fieldConfigEntrySchema).describe(
71450
+ "Ordered array of field config entries. Order = display order, visibility = per-entry. Fields not listed are hidden."
71451
+ ),
71452
+ sort: tableRecordSortSchema.nullable().describe("Sorting configuration for records in this view"),
71453
+ filters: tableRecordFiltersGroupNodeSchema.nullable().describe("Filter conditions applied to records in this view"),
71454
+ created_at: zod_default.string().describe("Timestamp when view was created")
71455
+ });
71456
+ var tableWithViewsSchema = tableSchema.extend({
71457
+ views: zod_default.array(tableViewSchema).describe("Array of views for this table")
71458
+ });
71459
+ var explorerViewSchema = zod_default.object({
71460
+ id: zod_default.string(),
71461
+ name: zod_default.string(),
71462
+ owner_member_id: zod_default.string(),
71463
+ // Per-view sidebar declutter list (NOT access control). null = every member
71464
+ // with table access sees this view; a non-empty member/group list restricts
71465
+ // it. Surfaced here so the table share dialog can render and edit each view's
71466
+ // audience without a second round-trip.
71467
+ visible_to: visibleToSchema.optional(),
71468
+ // Per-requesting-member sidebar preference: true if this member has hidden
71469
+ // the view. Hiding is UX-only — it never affects access or other members.
71470
+ hidden: zod_default.boolean()
71471
+ });
71472
+ var explorerTableSchema = zod_default.object({
71473
+ id: zod_default.string(),
71474
+ name: zod_default.string(),
71475
+ position: zod_default.number().int().nullable().optional(),
71476
+ views: zod_default.array(explorerViewSchema),
71477
+ owner_member_id: zod_default.string(),
71478
+ // Active package installations whose binding includes this table — the
71479
+ // sidebar marks these so members see the structure is app-managed before
71480
+ // they try to change it. Absent for unbound tables.
71481
+ package_apps: zod_default.array(zod_default.object({ id: zod_default.string(), name: zod_default.string() })).optional()
71482
+ });
71483
+ var documentTemplateTypeSchema = zod_default.enum(["html", "pdf-form", "excel", "word", "email"]).describe("Template type: 'html' for HTML templates, 'pdf-form' for fillable PDF forms, 'excel' for Excel templates, 'word' for Word templates, 'email' for email HTML templates");
71484
+ var documentTemplateSchema = zod_default.object({
71485
+ id: zod_default.string().describe("Unique identifier for the document template"),
71486
+ workspace_id: zod_default.string().describe("ID of the workspace this template belongs to"),
71487
+ name: zod_default.string().describe("Display name of the template"),
71488
+ type: documentTemplateTypeSchema,
71489
+ template: zod_default.string().describe("Template content with variable placeholders (e.g., {{customer_name}})"),
71490
+ format: zod_default.string().describe("Supported document formats.").optional(),
71491
+ orientation: zod_default.enum(["portrait", "landscape"]).describe("Page orientation for PDF generation").optional(),
71492
+ variables: zod_default.record(zod_default.string(), zod_default.unknown()).describe("Data to pass to the template."),
71493
+ current_version: zod_default.number().describe("Current version number of the template"),
71494
+ created_at: zod_default.string().describe("Timestamp when template was created"),
71495
+ template_url: zod_default.string().nullable().describe("Signed URL to download the template file (pdf-form/excel only)")
71496
+ });
71497
+
71498
+ // ../shared/src/schemas/packages.ts
71499
+ var contractAliasSchema = zod_default.string().regex(
71500
+ /^[a-z][a-z0-9_]*$/,
71501
+ "alias must be a lowercase slug: start with a letter, then letters/digits/underscores"
71502
+ );
71503
+ var runtimeAliasSchema = zod_default.string().min(1).max(200);
71504
+ var contractFieldBaseSchema = zod_default.object({
71505
+ alias: contractAliasSchema.describe("Stable local alias, unique within the entity"),
71506
+ // No braces: scaffold translates formula expressions from `{alias}` to the
71507
+ // platform's `{Field Label}` reference form, so a brace inside a label would
71508
+ // corrupt every formula that references the field.
71509
+ label: zod_default.string().min(1).regex(/^[^{}]*$/, "field labels cannot contain { or }").describe("Display label used as the field name at scaffold time"),
71510
+ description: zod_default.string().optional(),
71511
+ required: zod_default.boolean().optional().describe(
71512
+ "Advisory only: marks the field required for app-form/workflow UX. The table data model has no required constraint, so scaffold does NOT enforce it at the schema level \u2014 validation lives in the app/workflow layer."
71513
+ )
71514
+ });
71515
+ var contractSelectOptionSchema = zod_default.object({
71516
+ alias: contractAliasSchema.describe("Stable local alias, unique within the field"),
71517
+ label: zod_default.string().min(1).describe("Display label for the option"),
71518
+ color: optionColorSchema
71519
+ });
71520
+ var contractAutonumberConfigSchema = tableAutonumberFieldSchema.pick({
71521
+ prefix: true,
71522
+ padding: true,
71523
+ template: true
71524
+ });
71525
+ var contractTextFieldSchema = contractFieldBaseSchema.extend({
71526
+ type: zod_default.literal("text"),
71527
+ unique: zod_default.boolean().optional().describe("Unique values required"),
71528
+ format: textFormatSchema.optional()
71529
+ });
71530
+ var contractNumberFieldSchema = contractFieldBaseSchema.extend({
71531
+ type: zod_default.literal("number"),
71532
+ format: numberFormatSchema.optional(),
71533
+ currency: zod_default.string().optional().describe("ISO 4217 code")
71534
+ });
71535
+ var contractDateFieldSchema = contractFieldBaseSchema.extend({
71536
+ type: zod_default.literal("date"),
71537
+ format: dateFormatSchema.optional(),
71538
+ timezone: zod_default.string().optional().describe("IANA timezone"),
71539
+ derive_from: zod_default.enum(["created_at", "updated_at"]).optional().describe("Auto-populate from the row's system timestamp; the field becomes read-only.")
71540
+ });
71541
+ var contractBooleanFieldSchema = contractFieldBaseSchema.extend({
71542
+ type: zod_default.literal("boolean")
71543
+ });
71544
+ var contractSelectFieldSchema = contractFieldBaseSchema.extend({
71545
+ type: zod_default.literal("select"),
71546
+ options: zod_default.array(contractSelectOptionSchema).min(1),
71547
+ multi: zod_default.boolean().optional().describe("Allow multiple selections")
71548
+ });
71549
+ var contractSelectMemberFieldSchema = contractFieldBaseSchema.extend({
71550
+ type: zod_default.literal("select_member"),
71551
+ multi: zod_default.boolean().optional().describe("Allow multiple selections")
71552
+ });
71553
+ var contractSelectRecordLinkFieldSchema = contractFieldBaseSchema.extend({
71554
+ type: zod_default.literal("select_record_link"),
71555
+ target_entity: contractAliasSchema.describe("Alias of the entity this field links to"),
71556
+ sync_both_ways: zod_default.boolean().optional().describe("Create a paired link field on the target entity for bidirectional sync"),
71557
+ paired_field_alias: contractAliasSchema.optional().describe(
71558
+ "The pair edge of a bidirectional link: the field alias ON THE TARGET ENTITY that is this link's sync partner. Both sides of a pair carry it, each naming the other. Scaffold creates whichever side it reaches first WITH the pairing (the platform auto-creates the partner) and binds the partner alias to the auto-created field \u2014 without this edge the two contract fields would be created independently and collide with the auto-created partner."
71559
+ ),
71560
+ cardinality: zod_default.enum(["one", "many"]).optional().describe("How many linked records this field holds. Default 'many'."),
71561
+ display_field_aliases: zod_default.array(contractAliasSchema).optional().describe("Field aliases on the target entity shown as the link's display text / picker columns")
71562
+ });
71563
+ var contractFilesFieldSchema = contractFieldBaseSchema.extend({
71564
+ type: zod_default.literal("files")
71565
+ });
71566
+ var contractFormulaFieldSchema = contractFieldBaseSchema.extend({
71567
+ type: zod_default.literal("formula"),
71568
+ formula: formulaInputSchema.describe(
71569
+ "Formula config. The expression references other fields on the SAME entity by alias in braces, e.g. `{quantity} * {unit_price}`."
71570
+ )
71571
+ });
71572
+ var contractRollupFieldSchema = contractFieldBaseSchema.extend({
71573
+ type: zod_default.literal("rollup"),
71574
+ source_field_alias: contractAliasSchema.describe(
71575
+ "Alias of a select_record_link field on this entity to roll up from"
71576
+ ),
71577
+ aggregate_option: aggregateOptionSchema.describe(
71578
+ "Aggregation operation. Its `field_key` names a field alias on the linked entity."
71579
+ )
71580
+ });
71581
+ var contractLookupFieldSchema = contractFieldBaseSchema.extend({
71582
+ type: zod_default.literal("lookup"),
71583
+ source_field_alias: contractAliasSchema.describe(
71584
+ "Alias of a select_record_link field on this entity to look up through"
71585
+ ),
71586
+ lookup_field_alias: contractAliasSchema.describe(
71587
+ "Alias of the field on the linked entity to display"
71588
+ ),
71589
+ order_by: lookupOrderBySchema.optional().describe("Ordered lookup. Its `field_key` names a field alias on the linked entity.")
71590
+ });
71591
+ var contractAutonumberFieldSchema = contractFieldBaseSchema.extend({
71592
+ type: zod_default.literal("autonumber"),
71593
+ ...contractAutonumberConfigSchema.shape
71594
+ });
71595
+ var contractFieldSchema = zod_default.discriminatedUnion("type", [
71596
+ contractTextFieldSchema,
71597
+ contractNumberFieldSchema,
71598
+ contractDateFieldSchema,
71599
+ contractBooleanFieldSchema,
71600
+ contractSelectFieldSchema,
71601
+ contractSelectMemberFieldSchema,
71602
+ contractSelectRecordLinkFieldSchema,
71603
+ contractFilesFieldSchema,
71604
+ contractFormulaFieldSchema,
71605
+ contractRollupFieldSchema,
71606
+ contractLookupFieldSchema,
71607
+ contractAutonumberFieldSchema
71608
+ ]);
71609
+ var contractEntitySchema = zod_default.object({
71610
+ alias: contractAliasSchema.describe("Stable entity alias, unique within the contract"),
71611
+ label: zod_default.string().min(1).describe("Display label used as the table name at scaffold time"),
71612
+ description: zod_default.string().optional(),
71613
+ fields: zod_default.array(contractFieldSchema).min(1)
71614
+ });
71615
+ var contractRoleSchema = zod_default.object({
71616
+ alias: contractAliasSchema.describe("Stable role alias, bound to a workspace group at install"),
71617
+ label: zod_default.string().min(1)
71618
+ });
71619
+ var contractTemplateBaseSchema = zod_default.object({
71620
+ alias: contractAliasSchema.describe("Stable template alias, unique within the contract"),
71621
+ label: zod_default.string().min(1),
71622
+ content_sha256: zod_default.string().optional().describe(
71623
+ "sha256 (64-char lowercase hex) of the template content \u2014 inline: the utf-8 content string; file-backed: the bytes_ref bytes. The modified-detection reference. Optional in the schema so contracts published before template sha still parse; REQUIRED at publish (validatePackageContract)."
71624
+ )
71625
+ });
71626
+ var contractInlineTemplateSchema = (templateType) => contractTemplateBaseSchema.extend({
71627
+ type: zod_default.literal(templateType),
71628
+ content: zod_default.string().min(1).describe("Inline template content; placeholders reference field aliases")
71629
+ });
71630
+ var contractFileTemplateSchema = (templateType) => contractTemplateBaseSchema.extend({
71631
+ type: zod_default.literal(templateType),
71632
+ bytes_ref: zod_default.string().min(1).describe("Path of the backing file within the package bundle; markers reference field aliases")
71633
+ });
71634
+ var contractTemplateSchema = zod_default.discriminatedUnion("type", [
71635
+ contractInlineTemplateSchema("html"),
71636
+ contractInlineTemplateSchema("email"),
71637
+ contractFileTemplateSchema("excel"),
71638
+ contractFileTemplateSchema("word"),
71639
+ contractFileTemplateSchema("pdf-form")
71640
+ ]);
71641
+ var contractFromTableNodeSchema = zod_default.object({
71642
+ kind: zod_default.literal("from_table"),
71643
+ from_entity: contractAliasSchema.describe("Entity alias this query reads from"),
71644
+ filter: tableRecordFiltersSchema.optional(),
71645
+ search: zod_default.string().optional(),
71646
+ sort: tableRecordSortSchema.optional(),
71647
+ limit: zod_default.number().optional()
71648
+ });
71649
+ var contractProjectNodeSchema = zod_default.object({
71650
+ kind: zod_default.literal("project"),
71651
+ from: zod_default.lazy(() => contractQueryNodeSchema),
71652
+ columns: zod_default.array(queryProjectionColumnSchema).min(1)
71653
+ });
71654
+ var contractFilterNodeSchema = zod_default.object({
71655
+ kind: zod_default.literal("filter"),
71656
+ from: zod_default.lazy(() => contractQueryNodeSchema),
71657
+ predicate: tableRecordFiltersSchema
71658
+ });
71659
+ var contractJoinNodeSchema = zod_default.object({
71660
+ kind: zod_default.literal("join"),
71661
+ left: zod_default.lazy(() => contractQueryNodeSchema),
71662
+ right: zod_default.lazy(() => contractQueryNodeSchema),
71663
+ on: queryJoinPredicateSchema,
71664
+ type: zod_default.enum(["inner", "left"])
71665
+ });
71666
+ var contractUnionNodeSchema = zod_default.object({
71667
+ kind: zod_default.literal("union"),
71668
+ sources: zod_default.array(zod_default.lazy(() => contractQueryNodeSchema)).min(2)
71669
+ });
71670
+ var contractGroupNodeSchema = zod_default.object({
71671
+ kind: zod_default.literal("group"),
71672
+ from: zod_default.lazy(() => contractQueryNodeSchema),
71673
+ by: zod_default.array(zod_default.string()),
71674
+ aggregates: zod_default.array(queryAggregateColumnSchema).min(1)
71675
+ });
71676
+ var contractWindowNodeSchema = zod_default.object({
71677
+ kind: zod_default.literal("window"),
71678
+ from: zod_default.lazy(() => contractQueryNodeSchema),
71679
+ partition_by: zod_default.array(zod_default.string()),
71680
+ order_by: tableRecordSortSchema,
71681
+ frame: queryWindowFrameSchema.optional(),
71682
+ aggregates: zod_default.array(queryAggregateColumnSchema).optional(),
71683
+ functions: zod_default.array(queryWindowFunctionColumnSchema).optional()
71684
+ });
71685
+ var contractSortNodeSchema = zod_default.object({
71686
+ kind: zod_default.literal("sort"),
71687
+ from: zod_default.lazy(() => contractQueryNodeSchema),
71688
+ by: tableRecordSortSchema
71689
+ });
71690
+ var contractLimitNodeSchema = zod_default.object({
71691
+ kind: zod_default.literal("limit"),
71692
+ from: zod_default.lazy(() => contractQueryNodeSchema),
71693
+ n: zod_default.number().int().positive(),
71694
+ offset: zod_default.number().int().nonnegative().optional()
71695
+ });
71696
+ var contractUnpivotNodeSchema = zod_default.object({
71697
+ kind: zod_default.literal("unpivot"),
71698
+ from: zod_default.lazy(() => contractQueryNodeSchema),
71699
+ passthrough: zod_default.array(queryUnpivotPassthroughColumnSchema),
71700
+ row_columns: zod_default.array(queryUnpivotRowColumnSchema).min(1),
71701
+ rows: zod_default.array(zod_default.record(zod_default.string(), querySourceSchema)).min(1)
71702
+ });
71703
+ var contractUnnestNodeSchema = zod_default.object({
71704
+ kind: zod_default.literal("unnest"),
71705
+ from: zod_default.lazy(() => contractQueryNodeSchema),
71706
+ source: zod_default.string(),
71707
+ output: zod_default.string(),
71708
+ display_output: zod_default.string().optional(),
71709
+ keep_empty: zod_default.boolean().optional()
71710
+ });
71711
+ var contractQueryNodeSchema = zod_default.lazy(
71712
+ () => zod_default.discriminatedUnion("kind", [
71713
+ contractFromTableNodeSchema,
71714
+ contractProjectNodeSchema,
71715
+ contractFilterNodeSchema,
71716
+ contractJoinNodeSchema,
71717
+ contractUnionNodeSchema,
71718
+ contractGroupNodeSchema,
71719
+ contractWindowNodeSchema,
71720
+ contractSortNodeSchema,
71721
+ contractLimitNodeSchema,
71722
+ contractUnpivotNodeSchema,
71723
+ contractUnnestNodeSchema
71724
+ ])
71725
+ );
71726
+ var contractQuerySchema = zod_default.object({
71727
+ alias: runtimeAliasSchema.describe(
71728
+ "Stable query alias, unique within the contract \u2014 carried verbatim from the app (source invokes it at runtime)"
71729
+ ),
71730
+ params: zod_default.record(zod_default.string(), appWorkflowInputSchema).optional().describe("Typed param schema. Keys are param names referenced as {{params.<name>}} in the ast."),
71731
+ ast: contractQueryNodeSchema
71732
+ });
71733
+ var contractLifecycleEventSchema = tableWorkflowEventSchema;
71734
+ var contractWorkflowTriggerSchema = zod_default.discriminatedUnion("type", [
71735
+ zod_default.object({ type: zod_default.literal("app") }),
71736
+ zod_default.object({
71737
+ type: zod_default.literal("entity_lifecycle"),
71738
+ entity: contractAliasSchema.describe("Alias of the entity whose lifecycle fires this workflow"),
71739
+ event: contractLifecycleEventSchema
71740
+ })
71741
+ ]);
71742
+ var contractWorkflowSchema = zod_default.object({
71743
+ alias: runtimeAliasSchema.describe(
71744
+ "Stable workflow alias, unique within the contract \u2014 app-trigger aliases are carried verbatim from the app (source invokes them at runtime)"
71745
+ ),
71746
+ trigger: contractWorkflowTriggerSchema,
71747
+ inputs: zod_default.record(zod_default.string(), appWorkflowInputSchema).optional().describe("Typed inputs. record_link `table_id` names an entity alias; member `group` a role alias."),
71748
+ outputs: zod_default.record(zod_default.string(), appWorkflowOutputSchema).optional().describe("Typed outputs. record_link `table_id` names an entity alias."),
71749
+ body: zod_default.string().min(1).describe("Alias-form JS-subset workflow source. Identifiers are resolved against the binding at materialize time.")
71750
+ });
71751
+ var contractAgentSchema = appAgentDeclarationSchema.extend({
71752
+ alias: runtimeAliasSchema.describe(
71753
+ "Stable agent alias, unique within the contract \u2014 carried verbatim from the app (source invokes it at runtime)"
71754
+ ),
71755
+ knowledge_expects: zod_default.array(zod_default.string().min(1)).optional().describe(
71756
+ "Doc NAMES this agent must read that the package does NOT own \u2014 the installer's own docs. Resolved by name against the installing workspace: exactly one non-archived match binds, zero or several bind nothing and surface as an install warning, because doc names are not unique and picking one would be a guess. The agent installs either way."
71757
+ ),
71758
+ knowledge_aliases: zod_default.array(contractAliasSchema).optional().describe(
71759
+ "Package knowledge docs this agent may read, by contract alias. Materialize resolves each against `binding.knowledge` to the installing workspace's own doc id and writes the agent's `knowledge_doc_ids`. Omit for an agent that reads no package knowledge \u2014 an agent whose declared set does not travel can read NOTHING, since a declared set bounds every read."
71760
+ )
71761
+ });
71762
+ var contractConfigBaseSchema = zod_default.object({
71763
+ alias: contractAliasSchema.describe("Stable config alias, unique within the contract"),
71764
+ label: zod_default.string().min(1),
71765
+ description: zod_default.string().optional()
71766
+ });
71767
+ var contractTextConfigSchema = contractConfigBaseSchema.extend({
71768
+ type: zod_default.literal("text"),
71769
+ default: zod_default.string()
71770
+ });
71771
+ var contractBooleanConfigSchema = contractConfigBaseSchema.extend({
71772
+ type: zod_default.literal("boolean"),
71773
+ default: zod_default.boolean()
71774
+ });
71775
+ var contractColorConfigSchema = contractConfigBaseSchema.extend({
71776
+ type: zod_default.literal("color"),
71777
+ default: optionColorSchema
71778
+ });
71779
+ var contractNumberConfigSchema = contractConfigBaseSchema.extend({
71780
+ type: zod_default.literal("number"),
71781
+ default: zod_default.number()
71782
+ });
71783
+ var contractSelectConfigSchema = contractConfigBaseSchema.extend({
71784
+ type: zod_default.literal("select"),
71785
+ options: zod_default.array(zod_default.object({ value: zod_default.string(), label: zod_default.string().min(1) })).min(1),
71786
+ default: zod_default.string()
71787
+ });
71788
+ var contractConfigEntrySchema = zod_default.discriminatedUnion("type", [
71789
+ contractTextConfigSchema,
71790
+ contractBooleanConfigSchema,
71791
+ contractColorConfigSchema,
71792
+ contractNumberConfigSchema,
71793
+ contractSelectConfigSchema
71794
+ ]);
71795
+ var packageKindSchema = zod_default.enum(["app", "content"]);
71796
+ var contractKnowledgeEntrySchema = zod_default.object({
71797
+ name: zod_default.string().min(1).describe(
71798
+ "Display name \u2014 the runtime routing key an agent resolves the doc by; unique across the contract's knowledge"
71799
+ ),
71800
+ description: zod_default.string().describe("Doc description (may be empty)"),
71801
+ // Optional so contracts published before folders still parse; absent means
71802
+ // root. A curated corpus is exactly the case where organization is authored
71803
+ // deliberately, so install and upgrade have to carry it — otherwise every
71804
+ // installed corpus lands flat and the customer re-files it by hand.
71805
+ path: zod_default.string().optional().describe("Folder the doc is filed under; omitted means root"),
71806
+ content_ref: zod_default.string().min(1).describe("Path of the content file within the bundle \u2014 must be `knowledge/<alias>.md`"),
71807
+ content_sha256: zod_default.string().describe("sha256 (64-char lowercase hex) of the content bytes \u2014 the modified-detection reference")
71808
+ });
71809
+ var contractKnowledgeSchema = zod_default.record(contractAliasSchema, contractKnowledgeEntrySchema);
71810
+ var packageContractSchema = zod_default.object({
71811
+ entities: zod_default.array(contractEntitySchema).default([]),
71812
+ roles: zod_default.array(contractRoleSchema).default([]),
71813
+ templates: zod_default.array(contractTemplateSchema).default([]),
71814
+ queries: zod_default.array(contractQuerySchema).default([]),
71815
+ workflows: zod_default.array(contractWorkflowSchema).default([]),
71816
+ agents: zod_default.array(contractAgentSchema).default([]),
71817
+ config: zod_default.array(contractConfigEntrySchema).default([]),
71818
+ // Package-managed knowledge docs (alias → metadata + content sha). Defaults to
71819
+ // {} so contracts published before knowledge packaging still parse.
71820
+ knowledge: contractKnowledgeSchema.default({}),
71821
+ // Doc NAMES the package's agents route to but the package does not own —
71822
+ // install/health WARN (advisory) when a name has no doc in the workspace.
71823
+ // The first instance of the general cross-package "expects" contract.
71824
+ knowledge_expects: zod_default.array(zod_default.string()).default([])
71825
+ });
71826
+ var bindingMapSchema = zod_default.record(zod_default.string().min(1), zod_default.string().min(1));
71827
+ var bindingSchema = zod_default.object({
71828
+ entities: bindingMapSchema.default({}),
71829
+ fields: bindingMapSchema.default({}),
71830
+ options: bindingMapSchema.default({}),
71831
+ templates: bindingMapSchema.default({}),
71832
+ roles: bindingMapSchema.default({}),
71833
+ // Artifact registry, not a schema binding: the package-owned table-workflow
71834
+ // ROW id per entity_lifecycle workflow alias. Materialize matches/updates by
71835
+ // this id (a display name is forgeable and renameable), upgrade prunes by
71836
+ // it, and hashes look rows up through it. Excluded from BINDING_NAMESPACES:
71837
+ // a deleted row is a modified-core finding (fingerprints), never binding
71838
+ // drift, so validateBinding/applyDriftResolutions ignore it.
71839
+ workflows: bindingMapSchema.default({}),
71840
+ // The app's BUNDLED knowledge corpus: contract knowledge alias → the
71841
+ // workspace's concrete `kdc_` id. One row, one pin — the app installation IS
71842
+ // the anchor for its bundled docs (`package_content_installations` rows are
71843
+ // standalone content packages only). Excluded from BINDING_NAMESPACES: a
71844
+ // gone/edited doc is detected by the knowledge upgrade preview (live doc +
71845
+ // content-sha reads), never by validateBinding's live-id sweep.
71846
+ knowledge: bindingMapSchema.default({})
71847
+ });
71848
+ var BINDING_NAMESPACES = ["entities", "fields", "options", "templates", "roles"];
71849
+ var contentBindingMapSchema = zod_default.record(zod_default.string().min(1), zod_default.string().min(1));
71850
+ var packageContentBindingSchema = zod_default.object({
71851
+ knowledge: contentBindingMapSchema.default({}),
71852
+ templates: contentBindingMapSchema.default({})
71853
+ });
71854
+ var bindingDriftSchema = zod_default.object({
71855
+ namespace: zod_default.enum(BINDING_NAMESPACES),
71856
+ alias: zod_default.string(),
71857
+ id: zod_default.string().describe("The stale concrete id the alias was bound to")
71858
+ });
71859
+ var driftResolutionSchema = zod_default.union([
71860
+ zod_default.literal("recreate"),
71861
+ zod_default.object({ bind_to: zod_default.string().min(1) })
71862
+ ]);
71863
+ var MODIFIED_ARTIFACT_KINDS = ["queries", "workflows", "agents", "template"];
71864
+ var modifiedArtifactSchema = zod_default.object({
71865
+ kind: zod_default.enum(MODIFIED_ARTIFACT_KINDS),
71866
+ alias: zod_default.string(),
71867
+ // Templates only: the OLD pinned contract carried no content_sha256, so the
71868
+ // preview cannot attribute the divergence from the target to a local edit vs.
71869
+ // the older upstream content — a one-time consent, converging once a
71870
+ // sha-carrying version is pinned. Surfaces can say "can't verify local edits
71871
+ // (older package version)".
71872
+ baseline_unknown: zod_default.boolean().optional()
71873
+ });
71874
+ var packageArtifactHashesSchema = zod_default.object({
71875
+ queries: zod_default.record(zod_default.string(), zod_default.string()),
71876
+ workflows: zod_default.record(zod_default.string(), zod_default.string()),
71877
+ agents: zod_default.record(zod_default.string(), zod_default.string())
71878
+ });
71879
+ var modificationResolutionSchema = zod_default.union([zod_default.literal("revert"), zod_default.literal("keep")]);
71880
+ var KNOWLEDGE_UPGRADE_CHANGES = ["added", "changed", "removed", "drifted"];
71881
+ var knowledgeUpgradeEntrySchema = zod_default.object({
71882
+ alias: zod_default.string(),
71883
+ name: zod_default.string().describe("The doc's display name \u2014 the target contract's name, or the live name for a drifted doc"),
71884
+ change: zod_default.enum(KNOWLEDGE_UPGRADE_CHANGES),
71885
+ /** `changed`/`removed` only: the live doc's content diverged from the OLD pinned sha. */
71886
+ modified: zod_default.boolean(),
71887
+ /** The bound live doc id; null for `added`, or for a `drifted` doc that is gone. */
71888
+ doc_id: zod_default.string().nullable()
71889
+ });
71890
+ function knowledgeEntryNeedsConsent(entry) {
71891
+ if (entry.change === "drifted") return true;
71892
+ if (entry.change === "changed" || entry.change === "removed") return entry.modified;
71893
+ return false;
71894
+ }
71895
+ var knowledgeResolutionSchema = zod_default.enum(["apply", "keep", "archive", "recreate", "unbind"]);
71896
+ var upgradeResolutionsSchema = zod_default.record(
71897
+ zod_default.string(),
71898
+ zod_default.union([driftResolutionSchema, modificationResolutionSchema, knowledgeResolutionSchema])
71899
+ );
71900
+ var knowledgeBindConsentSchema = zod_default.record(zod_default.string(), zod_default.string());
71901
+ var knowledgeUpgradeResolutionsSchema = zod_default.object({
71902
+ resolutions: zod_default.record(zod_default.string(), knowledgeResolutionSchema).default({}),
71903
+ bind_to: knowledgeBindConsentSchema.default({})
71904
+ });
71905
+ var knowledgeBoundRefSchema = zod_default.object({
71906
+ alias: zod_default.string(),
71907
+ name: zod_default.string(),
71908
+ /** The bound doc id; null when the bound doc is gone/archived (drift). */
71909
+ doc_id: zod_default.string().nullable()
71910
+ });
71911
+ var appInstallationConfigValueSchema = zod_default.union([zod_default.string(), zod_default.number(), zod_default.boolean()]);
71912
+ var appInstallationConfigSchema = zod_default.record(
71913
+ contractAliasSchema,
71914
+ appInstallationConfigValueSchema
71915
+ );
71916
+ var appInstallationProvenanceSchema = zod_default.enum(["install", "adopt"]);
71917
+
70466
71918
  // ../shared/src/agent_instructions.ts
70467
71919
  function agentFilePath(alias) {
70468
71920
  return `src/agents/${alias}.md`;
@@ -70570,7 +72022,7 @@ function syncedEntry(entry) {
70570
72022
  function readSynced(projectDir) {
70571
72023
  try {
70572
72024
  const pkg2 = JSON.parse(
70573
- fs6.readFileSync(path7.join(projectDir, "package.json"), "utf-8")
72025
+ fs8.readFileSync(path9.join(projectDir, "package.json"), "utf-8")
70574
72026
  );
70575
72027
  const raw = pkg2.lotics?.synced ?? {};
70576
72028
  return {
@@ -70591,16 +72043,16 @@ function normalizeSyncedMap(map3) {
70591
72043
  );
70592
72044
  }
70593
72045
  function writeSynced(projectDir, kind, alias, entry) {
70594
- const pkgPath2 = path7.join(projectDir, "package.json");
70595
- const pkg2 = JSON.parse(fs6.readFileSync(pkgPath2, "utf-8"));
72046
+ const pkgPath2 = path9.join(projectDir, "package.json");
72047
+ const pkg2 = JSON.parse(fs8.readFileSync(pkgPath2, "utf-8"));
70596
72048
  if (!pkg2.lotics) return;
70597
72049
  pkg2.lotics.synced ??= {};
70598
72050
  pkg2.lotics.synced[kind] = { ...pkg2.lotics.synced[kind], [alias]: entry };
70599
- fs6.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
72051
+ fs8.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
70600
72052
  }
70601
- var WORKFLOWS_DIR = path7.join("src", "workflows");
70602
- var WORKFLOW_GLOBALS_DIR = path7.join(".lotics", "workflows");
70603
- var AGENTS_DIR = path7.join("src", "agents");
72053
+ var WORKFLOWS_DIR = path9.join("src", "workflows");
72054
+ var WORKFLOW_GLOBALS_DIR = path9.join(".lotics", "workflows");
72055
+ var AGENTS_DIR = path9.join("src", "agents");
70604
72056
  var WORKFLOW_TSCONFIG_EXCLUDES = ["src/workflows", ".lotics/workflows"];
70605
72057
  var LOTICS_INCLUDE_GLOB = ".lotics/**/*";
70606
72058
  var STALE_LOTICS_INCLUDES = /* @__PURE__ */ new Set([".lotics", "./.lotics", ".lotics/"]);
@@ -70611,7 +72063,7 @@ function isMissingWorkflowBodyError(error52) {
70611
72063
  return /has no step source|cannot be rendered|unsupported step/i.test(error52);
70612
72064
  }
70613
72065
  function workflowFileHeader(alias) {
70614
- const refPath = path7.join("..", "..", WORKFLOW_GLOBALS_DIR, `${alias}.globals.d.ts`).split(path7.sep).join("/");
72066
+ const refPath = path9.join("..", "..", WORKFLOW_GLOBALS_DIR, `${alias}.globals.d.ts`).split(path9.sep).join("/");
70615
72067
  return `/// <reference path="${refPath}" />
70616
72068
  // Auto-pulled workflow body for "${alias}". Edit the BODY between the wrapper
70617
72069
  // lines below, then check + push with:
@@ -70625,35 +72077,35 @@ export {};
70625
72077
  `;
70626
72078
  }
70627
72079
  function workflowFilePath(projectDir, alias) {
70628
- return path7.join(projectDir, WORKFLOWS_DIR, `${alias}.ts`);
72080
+ return path9.join(projectDir, WORKFLOWS_DIR, `${alias}.ts`);
70629
72081
  }
70630
72082
  var GLOBALS_SUFFIX = ".globals.d.ts";
70631
72083
  function workflowGlobalsPath(projectDir, alias) {
70632
- return path7.join(projectDir, WORKFLOW_GLOBALS_DIR, `${alias}${GLOBALS_SUFFIX}`);
72084
+ return path9.join(projectDir, WORKFLOW_GLOBALS_DIR, `${alias}${GLOBALS_SUFFIX}`);
70633
72085
  }
70634
72086
  function pruneOrphanWorkflowGlobals(projectDir, declared) {
70635
- const dir = path7.join(projectDir, WORKFLOW_GLOBALS_DIR);
70636
- if (!fs6.existsSync(dir)) return [];
72087
+ const dir = path9.join(projectDir, WORKFLOW_GLOBALS_DIR);
72088
+ if (!fs8.existsSync(dir)) return [];
70637
72089
  const removed = [];
70638
- for (const entry of fs6.readdirSync(dir)) {
72090
+ for (const entry of fs8.readdirSync(dir)) {
70639
72091
  if (!entry.endsWith(GLOBALS_SUFFIX)) continue;
70640
72092
  const alias = entry.slice(0, -GLOBALS_SUFFIX.length);
70641
72093
  if (declared.has(alias)) continue;
70642
- fs6.unlinkSync(path7.join(dir, entry));
72094
+ fs8.unlinkSync(path9.join(dir, entry));
70643
72095
  removed.push(alias);
70644
72096
  }
70645
72097
  return removed.sort();
70646
72098
  }
70647
72099
  function orphanWorkflowBodies(projectDir, declared) {
70648
- const dir = path7.join(projectDir, WORKFLOWS_DIR);
70649
- if (!fs6.existsSync(dir)) return [];
70650
- return fs6.readdirSync(dir).filter((e) => e.endsWith(".ts") && !e.endsWith(".d.ts") && !e.endsWith(".test.ts")).map((e) => e.slice(0, -".ts".length)).filter((alias) => !declared.has(alias)).sort();
72100
+ const dir = path9.join(projectDir, WORKFLOWS_DIR);
72101
+ if (!fs8.existsSync(dir)) return [];
72102
+ return fs8.readdirSync(dir).filter((e) => e.endsWith(".ts") && !e.endsWith(".d.ts") && !e.endsWith(".test.ts")).map((e) => e.slice(0, -".ts".length)).filter((alias) => !declared.has(alias)).sort();
70651
72103
  }
70652
72104
  function writeWorkflowGlobals(projectDir, alias, dts) {
70653
- const dir = path7.join(projectDir, WORKFLOW_GLOBALS_DIR);
70654
- fs6.mkdirSync(dir, { recursive: true });
72105
+ const dir = path9.join(projectDir, WORKFLOW_GLOBALS_DIR);
72106
+ fs8.mkdirSync(dir, { recursive: true });
70655
72107
  const file2 = workflowGlobalsPath(projectDir, alias);
70656
- fs6.writeFileSync(file2, `${dts.replace(/\s+$/, "")}
72108
+ fs8.writeFileSync(file2, `${dts.replace(/\s+$/, "")}
70657
72109
  `);
70658
72110
  return file2;
70659
72111
  }
@@ -70661,26 +72113,26 @@ function normalizeWorkflowBody(source) {
70661
72113
  return source.replace(/\s+$/, "");
70662
72114
  }
70663
72115
  function writeWorkflowFile(projectDir, alias, source, envelope = { prefix: WORKFLOW_ENVELOPE_PREFIX, suffix: WORKFLOW_ENVELOPE_SUFFIX }) {
70664
- const dir = path7.join(projectDir, WORKFLOWS_DIR);
70665
- fs6.mkdirSync(dir, { recursive: true });
72116
+ const dir = path9.join(projectDir, WORKFLOWS_DIR);
72117
+ fs8.mkdirSync(dir, { recursive: true });
70666
72118
  const file2 = workflowFilePath(projectDir, alias);
70667
72119
  const body = normalizeWorkflowBody(source);
70668
- fs6.writeFileSync(file2, `${workflowFileHeader(alias)}
72120
+ fs8.writeFileSync(file2, `${workflowFileHeader(alias)}
70669
72121
  ${envelope.prefix}${body}${envelope.suffix}
70670
72122
  `);
70671
72123
  return file2;
70672
72124
  }
70673
72125
  function agentFilePath2(projectDir, alias) {
70674
- return path7.join(projectDir, ...agentFilePath(alias).split("/"));
72126
+ return path9.join(projectDir, ...agentFilePath(alias).split("/"));
70675
72127
  }
70676
72128
  var CLI_AGENT_NOTES = [
70677
72129
  "Pulled from the LIVE app row; edit here, then: lotics app agent set <alias>",
70678
72130
  "The typed fields (inputs/outputs/tool_names/model_tier) live in package.json#lotics.agents"
70679
72131
  ];
70680
72132
  function writeAgentFile(projectDir, alias, instructions) {
70681
- fs6.mkdirSync(path7.join(projectDir, AGENTS_DIR), { recursive: true });
72133
+ fs8.mkdirSync(path9.join(projectDir, AGENTS_DIR), { recursive: true });
70682
72134
  const file2 = agentFilePath2(projectDir, alias);
70683
- fs6.writeFileSync(
72135
+ fs8.writeFileSync(
70684
72136
  file2,
70685
72137
  wrapAgentFile({ alias, instructions, notes: CLI_AGENT_NOTES.map((n) => n.replace("<alias>", alias)) })
70686
72138
  );
@@ -70694,7 +72146,7 @@ function writeAgentFiles(projectDir, agents, opts) {
70694
72146
  console.error(`\u26A0 Skipped src/agents/${alias}.md \u2014 the live declaration carries no instructions.`);
70695
72147
  continue;
70696
72148
  }
70697
- if (!opts.overwrite && localPromptUnpushed(projectDir, alias)) {
72149
+ if (!opts.overwrite && localPromptUnpushed(projectDir, alias, instructions)) {
70698
72150
  opts.kept.push(`src/agents/${alias}.md`);
70699
72151
  continue;
70700
72152
  }
@@ -70703,22 +72155,26 @@ function writeAgentFiles(projectDir, agents, opts) {
70703
72155
  }
70704
72156
  return written;
70705
72157
  }
70706
- function localPromptUnpushed(projectDir, alias) {
72158
+ function localPromptUnpushed(projectDir, alias, live) {
70707
72159
  const file2 = agentFilePath2(projectDir, alias);
70708
- if (!fs6.existsSync(file2)) return false;
72160
+ if (!fs8.existsSync(file2)) return false;
72161
+ const onDisk = fs8.readFileSync(file2, "utf-8");
70709
72162
  const baseline = readSynced(projectDir).agents[alias]?.content;
70710
- if (baseline === void 0) return true;
70711
- return contentSha(stripAgentHeader(fs6.readFileSync(file2, "utf-8"))) !== baseline;
72163
+ if (baseline === void 0) return stripAgentHeader(onDisk).trim() !== live.trim();
72164
+ return contentSha(stripAgentHeader(onDisk)) !== baseline;
70712
72165
  }
70713
72166
  function generatedWorkflowDescription(appName, alias) {
70714
72167
  return `Workflow invoked by app "${appName}" via useWorkflow("${alias}").`;
70715
72168
  }
70716
- function localBodyUnpushed(projectDir, alias) {
72169
+ function localBodyUnpushed(projectDir, alias, live) {
70717
72170
  const file2 = workflowFilePath(projectDir, alias);
70718
- if (!fs6.existsSync(file2)) return false;
72171
+ if (!fs8.existsSync(file2)) return false;
72172
+ const onDisk = fs8.readFileSync(file2, "utf-8");
70719
72173
  const baseline = readSynced(projectDir).workflows[alias]?.content;
70720
- if (baseline === void 0) return true;
70721
- return contentSha(stripWorkflowHeader(fs6.readFileSync(file2, "utf-8"))) !== baseline;
72174
+ if (baseline === void 0) {
72175
+ return stripWorkflowHeader(onDisk).trim() !== normalizeWorkflowBody(live).trim();
72176
+ }
72177
+ return contentSha(stripWorkflowHeader(onDisk)) !== baseline;
70722
72178
  }
70723
72179
  async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows, opts) {
70724
72180
  const written = [];
@@ -70745,7 +72201,7 @@ async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows
70745
72201
  alias,
70746
72202
  toWorkflowDtsDeclaration(declaration)
70747
72203
  );
70748
- if (!opts.overwrite && localBodyUnpushed(projectDir, alias)) {
72204
+ if (!opts.overwrite && localBodyUnpushed(projectDir, alias, source)) {
70749
72205
  opts.kept.push(`src/workflows/${alias}.ts`);
70750
72206
  continue;
70751
72207
  }
@@ -70773,12 +72229,12 @@ async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows
70773
72229
  return written;
70774
72230
  }
70775
72231
  function writeWorkflowDescription(projectDir, alias, description) {
70776
- const pkgPath2 = path7.join(projectDir, "package.json");
70777
- const pkg2 = JSON.parse(fs6.readFileSync(pkgPath2, "utf-8"));
72232
+ const pkgPath2 = path9.join(projectDir, "package.json");
72233
+ const pkg2 = JSON.parse(fs8.readFileSync(pkgPath2, "utf-8"));
70778
72234
  const declaration = pkg2.lotics?.workflows?.[alias];
70779
72235
  if (!declaration) return;
70780
72236
  declaration.description = description;
70781
- fs6.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
72237
+ fs8.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
70782
72238
  }
70783
72239
  async function fetchWorkflowGlobals(client, projectDir, app_id, alias, declaration) {
70784
72240
  try {
@@ -70828,20 +72284,20 @@ function runNpm(args, cwd) {
70828
72284
  }
70829
72285
  function readAppWorkspaceId(projectDir) {
70830
72286
  try {
70831
- const pkgPath2 = path7.join(projectDir, "package.json");
70832
- if (!fs6.existsSync(pkgPath2)) return null;
70833
- const pkg2 = JSON.parse(fs6.readFileSync(pkgPath2, "utf-8"));
72287
+ const pkgPath2 = path9.join(projectDir, "package.json");
72288
+ if (!fs8.existsSync(pkgPath2)) return null;
72289
+ const pkg2 = JSON.parse(fs8.readFileSync(pkgPath2, "utf-8"));
70834
72290
  return pkg2.lotics?.workspace_id ?? null;
70835
72291
  } catch {
70836
72292
  return null;
70837
72293
  }
70838
72294
  }
70839
72295
  function readAppMeta(projectDir) {
70840
- const pkgPath2 = path7.join(projectDir, "package.json");
70841
- if (!fs6.existsSync(pkgPath2)) {
72296
+ const pkgPath2 = path9.join(projectDir, "package.json");
72297
+ if (!fs8.existsSync(pkgPath2)) {
70842
72298
  throw new Error(`No package.json found at ${projectDir}. Run 'lotics app create' first.`);
70843
72299
  }
70844
- const pkg2 = JSON.parse(fs6.readFileSync(pkgPath2, "utf-8"));
72300
+ const pkg2 = JSON.parse(fs8.readFileSync(pkgPath2, "utf-8"));
70845
72301
  if (!pkg2.lotics?.app_id || !pkg2.lotics.workspace_id) {
70846
72302
  throw new Error(
70847
72303
  `package.json is missing the 'lotics' field with app_id and workspace_id. Was this folder created with 'lotics app create'?`
@@ -70860,8 +72316,8 @@ function readAppMeta(projectDir) {
70860
72316
  };
70861
72317
  }
70862
72318
  function writeAppMeta(projectDir, meta3) {
70863
- const pkgPath2 = path7.join(projectDir, "package.json");
70864
- const pkg2 = JSON.parse(fs6.readFileSync(pkgPath2, "utf-8"));
72319
+ const pkgPath2 = path9.join(projectDir, "package.json");
72320
+ const pkg2 = JSON.parse(fs8.readFileSync(pkgPath2, "utf-8"));
70865
72321
  const existing = pkg2.lotics && typeof pkg2.lotics === "object" ? pkg2.lotics : {};
70866
72322
  const supplied = new Set(
70867
72323
  Object.entries(meta3).filter(([, value2]) => value2 !== void 0).map(([key]) => key)
@@ -70871,11 +72327,11 @@ function writeAppMeta(projectDir, meta3) {
70871
72327
  if (!supplied.has(key)) preserved[key] = value2;
70872
72328
  }
70873
72329
  pkg2.lotics = { ...meta3, ...preserved };
70874
- fs6.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
72330
+ fs8.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
70875
72331
  }
70876
72332
  function writeWorkflowOutputs(projectDir, alias, outputs) {
70877
- const pkgPath2 = path7.join(projectDir, "package.json");
70878
- const pkg2 = JSON.parse(fs6.readFileSync(pkgPath2, "utf-8"));
72333
+ const pkgPath2 = path9.join(projectDir, "package.json");
72334
+ const pkg2 = JSON.parse(fs8.readFileSync(pkgPath2, "utf-8"));
70879
72335
  const declaration = pkg2.lotics?.workflows?.[alias];
70880
72336
  if (!declaration) {
70881
72337
  throw new Error(
@@ -70883,7 +72339,7 @@ function writeWorkflowOutputs(projectDir, alias, outputs) {
70883
72339
  );
70884
72340
  }
70885
72341
  declaration.outputs = outputs;
70886
- fs6.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
72342
+ fs8.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
70887
72343
  }
70888
72344
  function workflowBodyDrift(projectDir, workflows, synced = {}) {
70889
72345
  const drifted = [];
@@ -70891,16 +72347,16 @@ function workflowBodyDrift(projectDir, workflows, synced = {}) {
70891
72347
  const baseline = syncedEntry(synced[alias])?.content;
70892
72348
  if (baseline === void 0) continue;
70893
72349
  const file2 = workflowFilePath(projectDir, alias);
70894
- if (!fs6.existsSync(file2)) continue;
70895
- const source = stripWorkflowHeader(fs6.readFileSync(file2, "utf-8"));
72350
+ if (!fs8.existsSync(file2)) continue;
72351
+ const source = stripWorkflowHeader(fs8.readFileSync(file2, "utf-8"));
70896
72352
  if (source.trim() === "" || contentSha(source) === baseline) continue;
70897
72353
  drifted.push(alias);
70898
72354
  }
70899
72355
  return drifted;
70900
72356
  }
70901
72357
  function ensureAppTsconfig(projectDir) {
70902
- const tsconfigPath = path7.join(projectDir, "tsconfig.json");
70903
- if (!fs6.existsSync(tsconfigPath)) {
72358
+ const tsconfigPath = path9.join(projectDir, "tsconfig.json");
72359
+ if (!fs8.existsSync(tsconfigPath)) {
70904
72360
  console.error(
70905
72361
  `\u26A0 No tsconfig.json at ${projectDir} \u2014 could not ensure "include" has "${LOTICS_INCLUDE_GLOB}" (a bare .lotics loads none of the generated types) or "exclude" has ${WORKFLOW_TSCONFIG_EXCLUDES.join(", ")}.`
70906
72362
  );
@@ -70908,7 +72364,7 @@ function ensureAppTsconfig(projectDir) {
70908
72364
  }
70909
72365
  let parsed;
70910
72366
  try {
70911
- parsed = JSON.parse(fs6.readFileSync(tsconfigPath, "utf-8"));
72367
+ parsed = JSON.parse(fs8.readFileSync(tsconfigPath, "utf-8"));
70912
72368
  } catch (err2) {
70913
72369
  console.error(
70914
72370
  `\u26A0 Could not parse tsconfig.json (${err2 instanceof Error ? err2.message : String(err2)}) \u2014 ensure "include" has "${LOTICS_INCLUDE_GLOB}" and "exclude" has ${WORKFLOW_TSCONFIG_EXCLUDES.join(", ")} manually.`
@@ -70941,7 +72397,7 @@ function ensureAppTsconfig(projectDir) {
70941
72397
  );
70942
72398
  }
70943
72399
  if (changes.length === 0) return;
70944
- fs6.writeFileSync(tsconfigPath, JSON.stringify(parsed, null, 2) + "\n");
72400
+ fs8.writeFileSync(tsconfigPath, JSON.stringify(parsed, null, 2) + "\n");
70945
72401
  console.error(`Patched tsconfig.json: ${changes.join("; ")}.`);
70946
72402
  }
70947
72403
  var LINK_TSCONFIG = ".lotics/tsconfig.link.json";
@@ -70951,15 +72407,15 @@ function writeDevLinkTsconfig(projectDir) {
70951
72407
  if (uiSrc) {
70952
72408
  paths["@lotics/ui/*"] = [`${uiSrc}/*`];
70953
72409
  for (const peer of readKitPeerNames(projectDir)) {
70954
- const typesDir = path7.join(projectDir, "node_modules", typesPackageFor(peer));
70955
- const runtimeDir = path7.join(projectDir, "node_modules", peer);
70956
- const target = fs6.existsSync(typesDir) ? `../node_modules/${typesPackageFor(peer)}` : fs6.existsSync(runtimeDir) ? `../node_modules/${peer}` : null;
72410
+ const typesDir = path9.join(projectDir, "node_modules", typesPackageFor(peer));
72411
+ const runtimeDir = path9.join(projectDir, "node_modules", peer);
72412
+ const target = fs8.existsSync(typesDir) ? `../node_modules/${typesPackageFor(peer)}` : fs8.existsSync(runtimeDir) ? `../node_modules/${peer}` : null;
70957
72413
  if (!target) continue;
70958
72414
  paths[peer] = [target];
70959
72415
  paths[`${peer}/*`] = [`${target}/*`];
70960
72416
  }
70961
72417
  }
70962
- const file2 = path7.join(projectDir, ".lotics", "tsconfig.link.json");
72418
+ const file2 = path9.join(projectDir, ".lotics", "tsconfig.link.json");
70963
72419
  const header = uiSrc ? `// GENERATED by \`lotics app codegen\` \u2014 do not edit.
70964
72420
  // LOTICS_UI_SRC is set, so @lotics/ui resolves to your working copy for tsc,
70965
72421
  // vitest, eslint and your editor \u2014 the same copy Vite is bundling. The peer
@@ -70970,45 +72426,45 @@ function writeDevLinkTsconfig(projectDir) {
70970
72426
  // LOTICS_UI_SRC is not set, so this is inert and @lotics/ui resolves from
70971
72427
  // node_modules as normal.
70972
72428
  `;
70973
- fs6.writeFileSync(file2, `${header}${JSON.stringify({ compilerOptions: { paths } }, null, 2)}
72429
+ fs8.writeFileSync(file2, `${header}${JSON.stringify({ compilerOptions: { paths } }, null, 2)}
70974
72430
  `);
70975
72431
  return file2;
70976
72432
  }
70977
72433
  function writeKitTypeAugmentation(projectDir) {
70978
72434
  const uiSrc = process.env.LOTICS_UI_SRC;
70979
- const source = uiSrc ? path7.join(uiSrc, "react_native.d.ts") : path7.join(projectDir, "node_modules", "@lotics", "ui", "src", "react_native.d.ts");
70980
- const target = path7.join(projectDir, ".lotics", "react_native.d.ts");
70981
- if (!fs6.existsSync(source)) {
70982
- if (fs6.existsSync(target)) fs6.rmSync(target);
72435
+ const source = uiSrc ? path9.join(uiSrc, "react_native.d.ts") : path9.join(projectDir, "node_modules", "@lotics", "ui", "src", "react_native.d.ts");
72436
+ const target = path9.join(projectDir, ".lotics", "react_native.d.ts");
72437
+ if (!fs8.existsSync(source)) {
72438
+ if (fs8.existsSync(target)) fs8.rmSync(target);
70983
72439
  return;
70984
72440
  }
70985
- fs6.writeFileSync(
72441
+ fs8.writeFileSync(
70986
72442
  target,
70987
72443
  `// GENERATED by \`lotics app codegen\` from @lotics/ui \u2014 do not edit.
70988
72444
  // The kit's web-only ViewStyle/TextStyle properties, which react-native does
70989
72445
  // not model. Delete any hand-copied src/react_native.d.ts; this replaces it.
70990
- ` + fs6.readFileSync(source, "utf-8")
72446
+ ` + fs8.readFileSync(source, "utf-8")
70991
72447
  );
70992
72448
  }
70993
72449
  function typesPackageFor(peer) {
70994
72450
  return peer.startsWith("@") ? `@types/${peer.slice(1).replace("/", "__")}` : `@types/${peer}`;
70995
72451
  }
70996
72452
  function readKitPeerNames(projectDir) {
70997
- const kitPkg = path7.join(projectDir, "node_modules", "@lotics", "ui", "package.json");
70998
- if (!fs6.existsSync(kitPkg)) return [];
70999
- const parsed = JSON.parse(fs6.readFileSync(kitPkg, "utf-8"));
72453
+ const kitPkg = path9.join(projectDir, "node_modules", "@lotics", "ui", "package.json");
72454
+ if (!fs8.existsSync(kitPkg)) return [];
72455
+ const parsed = JSON.parse(fs8.readFileSync(kitPkg, "utf-8"));
71000
72456
  const peers = parsed.peerDependencies;
71001
72457
  return peers && typeof peers === "object" ? Object.keys(peers) : [];
71002
72458
  }
71003
72459
  function writeAppDts(projectDir, manifest) {
71004
- const dotLotics = path7.join(projectDir, ".lotics");
71005
- fs6.mkdirSync(dotLotics, { recursive: true });
72460
+ const dotLotics = path9.join(projectDir, ".lotics");
72461
+ fs8.mkdirSync(dotLotics, { recursive: true });
71006
72462
  const written = [
71007
- [path7.join(dotLotics, "app_workflows.d.ts"), generateAppWorkflowsDts(manifest.workflows)],
71008
- [path7.join(dotLotics, "app_queries.d.ts"), generateAppQueriesDts(manifest.queries)],
71009
- [path7.join(dotLotics, "app_agents.d.ts"), generateAppAgentsDts(manifest.agents)]
72463
+ [path9.join(dotLotics, "app_workflows.d.ts"), generateAppWorkflowsDts(manifest.workflows)],
72464
+ [path9.join(dotLotics, "app_queries.d.ts"), generateAppQueriesDts(manifest.queries)],
72465
+ [path9.join(dotLotics, "app_agents.d.ts"), generateAppAgentsDts(manifest.agents)]
71010
72466
  ];
71011
- for (const [file2, content] of written) fs6.writeFileSync(file2, content);
72467
+ for (const [file2, content] of written) fs8.writeFileSync(file2, content);
71012
72468
  writeDevLinkTsconfig(projectDir);
71013
72469
  writeKitTypeAugmentation(projectDir);
71014
72470
  ensureAppTsconfig(projectDir);
@@ -71016,7 +72472,7 @@ function writeAppDts(projectDir, manifest) {
71016
72472
  return written.map(([file2]) => file2);
71017
72473
  }
71018
72474
  function readCodegenTablesAllowlist(projectDir) {
71019
- const pkg2 = JSON.parse(fs6.readFileSync(path7.join(projectDir, "package.json"), "utf-8"));
72475
+ const pkg2 = JSON.parse(fs8.readFileSync(path9.join(projectDir, "package.json"), "utf-8"));
71020
72476
  const tables = pkg2.lotics?.codegen?.tables;
71021
72477
  return Array.isArray(tables) ? tables.filter((t) => typeof t === "string") : [];
71022
72478
  }
@@ -71029,17 +72485,17 @@ function resolveCodegenTableIds(projectDir, queries) {
71029
72485
  return [...ids];
71030
72486
  }
71031
72487
  function writeAppFields(projectDir, tables) {
71032
- const dotLotics = path7.join(projectDir, ".lotics");
71033
- fs6.mkdirSync(dotLotics, { recursive: true });
71034
- const file2 = path7.join(dotLotics, "app_fields.ts");
71035
- fs6.writeFileSync(file2, generateAppFields(tables));
72488
+ const dotLotics = path9.join(projectDir, ".lotics");
72489
+ fs8.mkdirSync(dotLotics, { recursive: true });
72490
+ const file2 = path9.join(dotLotics, "app_fields.ts");
72491
+ fs8.writeFileSync(file2, generateAppFields(tables));
71036
72492
  return file2;
71037
72493
  }
71038
72494
  function writeBindingAppFields(projectDir, binding) {
71039
- const dotLotics = path7.join(projectDir, ".lotics");
71040
- fs6.mkdirSync(dotLotics, { recursive: true });
71041
- const file2 = path7.join(dotLotics, "app_fields.ts");
71042
- fs6.writeFileSync(file2, generatePackageAppFields(binding));
72495
+ const dotLotics = path9.join(projectDir, ".lotics");
72496
+ fs8.mkdirSync(dotLotics, { recursive: true });
72497
+ const file2 = path9.join(dotLotics, "app_fields.ts");
72498
+ fs8.writeFileSync(file2, generatePackageAppFields(binding));
71043
72499
  return file2;
71044
72500
  }
71045
72501
  async function writeGeneratedAppFields(client, projectDir, app, queries) {
@@ -71080,7 +72536,7 @@ async function writeGeneratedAppFields(client, projectDir, app, queries) {
71080
72536
  }
71081
72537
  function warnAppFieldsUnwritten(projectDir, err2) {
71082
72538
  const reason = err2 instanceof Error ? err2.message : String(err2);
71083
- if (fs6.existsSync(path7.join(projectDir, ".lotics", "app_fields.ts"))) {
72539
+ if (fs8.existsSync(path9.join(projectDir, ".lotics", "app_fields.ts"))) {
71084
72540
  console.error(`\u26A0 Could not regenerate .lotics/app_fields.ts (${reason}). Kept the existing file.`);
71085
72541
  return;
71086
72542
  }
@@ -71091,8 +72547,8 @@ function warnAppFieldsUnwritten(projectDir, err2) {
71091
72547
  function warnAboutDevLink(projectDir, command) {
71092
72548
  const uiSrc = process.env.LOTICS_UI_SRC;
71093
72549
  if (!uiSrc) return;
71094
- const viteConfigPath = path7.join(projectDir, "vite.config.ts");
71095
- const config2 = fs6.existsSync(viteConfigPath) ? fs6.readFileSync(viteConfigPath, "utf-8") : "";
72550
+ const viteConfigPath = path9.join(projectDir, "vite.config.ts");
72551
+ const config2 = fs8.existsSync(viteConfigPath) ? fs8.readFileSync(viteConfigPath, "utf-8") : "";
71096
72552
  const linked = config2.includes("loticsResolve") || config2.includes("LOTICS_UI_SRC");
71097
72553
  if (!linked) {
71098
72554
  console.error(
@@ -71110,19 +72566,19 @@ function warnAboutDevLink(projectDir, command) {
71110
72566
  }
71111
72567
  }
71112
72568
  function ensureAppVitestSetup(projectDir) {
71113
- const setupPath = path7.join(projectDir, VITEST_SETUP_FILENAME);
71114
- if (!fs6.existsSync(setupPath)) {
71115
- fs6.writeFileSync(setupPath, VITEST_SETUP_CONTENT);
72569
+ const setupPath = path9.join(projectDir, VITEST_SETUP_FILENAME);
72570
+ if (!fs8.existsSync(setupPath)) {
72571
+ fs8.writeFileSync(setupPath, VITEST_SETUP_CONTENT);
71116
72572
  console.error(`Wrote ${VITEST_SETUP_FILENAME} (stubs getAppBinding so \`npm test\` collects).`);
71117
- } else if (!fs6.readFileSync(setupPath, "utf-8").includes("getAppBinding")) {
72573
+ } else if (!fs8.readFileSync(setupPath, "utf-8").includes("getAppBinding")) {
71118
72574
  console.error(
71119
72575
  `\u26A0 ${VITEST_SETUP_FILENAME} exists but does not stub getAppBinding \u2014 \`npm test\` will fail to collect any test that imports the app graph (the binding-form .lotics/app_fields.ts awaits a network call). Add the stub to it:
71120
72576
  ${VITEST_SETUP_CONTENT}`
71121
72577
  );
71122
72578
  }
71123
- const viteConfigPath = path7.join(projectDir, "vite.config.ts");
71124
- if (!fs6.existsSync(viteConfigPath)) return;
71125
- const viteConfig = fs6.readFileSync(viteConfigPath, "utf-8");
72579
+ const viteConfigPath = path9.join(projectDir, "vite.config.ts");
72580
+ if (!fs8.existsSync(viteConfigPath)) return;
72581
+ const viteConfig = fs8.readFileSync(viteConfigPath, "utf-8");
71126
72582
  if (/setupFiles\s*:/.test(viteConfig) && viteConfig.includes(VITEST_SETUP_FILENAME)) return;
71127
72583
  console.error(
71128
72584
  `\u26A0 vite.config.ts does not wire ${VITEST_SETUP_FILENAME} \u2014 \`npm test\` will fail to collect any test that imports the app graph (the binding-form .lotics/app_fields.ts awaits a network call). Add it to the \`test\` block:
@@ -71130,7 +72586,7 @@ ${VITEST_SETUP_CONTENT}`
71130
72586
  );
71131
72587
  }
71132
72588
  async function appCodegen(args) {
71133
- const projectDir = path7.resolve(args.projectDir ?? process.cwd());
72589
+ const projectDir = path9.resolve(args.projectDir ?? process.cwd());
71134
72590
  const meta3 = readAppMeta(projectDir);
71135
72591
  const dtsPaths = writeAppDts(projectDir, {
71136
72592
  workflows: meta3.workflows,
@@ -71144,7 +72600,7 @@ async function appCodegen(args) {
71144
72600
  }
71145
72601
  for (const alias of orphanWorkflowBodies(projectDir, declared)) {
71146
72602
  console.error(
71147
- `\u26A0 ${path7.join(WORKFLOWS_DIR, `${alias}.ts`)} is not declared in package.json#lotics.workflows \u2014 'workflow check' and 'workflow set' both skip it. Declare the alias or delete the file.`
72603
+ `\u26A0 ${path9.join(WORKFLOWS_DIR, `${alias}.ts`)} is not declared in package.json#lotics.workflows \u2014 'workflow check' and 'workflow set' both skip it. Declare the alias or delete the file.`
71148
72604
  );
71149
72605
  }
71150
72606
  if (!args.client) {
@@ -71195,8 +72651,8 @@ async function refreshWorkflowGlobals(client, projectDir, app_id, workflows) {
71195
72651
  }
71196
72652
  async function refreshWorkflowTypes(client, projectDir, app_id, alias, declaration) {
71197
72653
  const file2 = workflowFilePath(projectDir, alias);
71198
- if (!fs6.existsSync(file2)) return false;
71199
- const body = stripWorkflowHeader(fs6.readFileSync(file2, "utf-8"));
72654
+ if (!fs8.existsSync(file2)) return false;
72655
+ const body = stripWorkflowHeader(fs8.readFileSync(file2, "utf-8"));
71200
72656
  if (body.trim() === "") return false;
71201
72657
  const envelope = await fetchWorkflowGlobals(client, projectDir, app_id, alias, declaration);
71202
72658
  writeWorkflowFile(projectDir, alias, body, envelope);
@@ -71226,21 +72682,21 @@ async function downloadToFile(url2, destPath) {
71226
72682
  throw new Error(`Failed to download ${url2}: ${response.status}`);
71227
72683
  }
71228
72684
  const buffer = Buffer.from(await response.arrayBuffer());
71229
- fs6.writeFileSync(destPath, buffer);
72685
+ fs8.writeFileSync(destPath, buffer);
71230
72686
  }
71231
72687
  function appDirName(name2) {
71232
72688
  const cleaned = name2.replace(/[/\\]+/g, "-").replace(/[<>:"|?*]/g, "").replace(/\s+/g, " ").trim().replace(/^[.\s-]+|[.\s-]+$/g, "");
71233
72689
  return cleaned || "app";
71234
72690
  }
71235
72691
  async function appCreate(client, args) {
71236
- const targetPath = path7.resolve(args.targetPath ?? appDirName(args.name));
71237
- if (fs6.existsSync(targetPath)) {
71238
- const entries2 = fs6.readdirSync(targetPath);
72692
+ const targetPath = path9.resolve(args.targetPath ?? appDirName(args.name));
72693
+ if (fs8.existsSync(targetPath)) {
72694
+ const entries2 = fs8.readdirSync(targetPath);
71239
72695
  if (entries2.length > 0) {
71240
72696
  throw new Error(`Target directory ${targetPath} is not empty.`);
71241
72697
  }
71242
72698
  }
71243
- fs6.mkdirSync(targetPath, { recursive: true });
72699
+ fs8.mkdirSync(targetPath, { recursive: true });
71244
72700
  const [uiLatest, sdkLatest] = await Promise.all([
71245
72701
  requireLatestNpmVersion("@lotics/ui"),
71246
72702
  requireLatestNpmVersion("@lotics/app-sdk")
@@ -71255,9 +72711,9 @@ async function appCreate(client, args) {
71255
72711
  sdk_version: `^${sdkLatest}`
71256
72712
  });
71257
72713
  for (const file2 of files) {
71258
- const fullPath = path7.join(targetPath, file2.path);
71259
- fs6.mkdirSync(path7.dirname(fullPath), { recursive: true });
71260
- fs6.writeFileSync(fullPath, file2.content);
72714
+ const fullPath = path9.join(targetPath, file2.path);
72715
+ fs8.mkdirSync(path9.dirname(fullPath), { recursive: true });
72716
+ fs8.writeFileSync(fullPath, file2.content);
71261
72717
  }
71262
72718
  console.error(`Scaffolded ${files.length} files into ${targetPath}`);
71263
72719
  writeAppDts(targetPath, {});
@@ -71270,8 +72726,10 @@ async function appCreate(client, args) {
71270
72726
  });
71271
72727
  console.error(`
71272
72728
  Ready. Next steps:`);
71273
- console.error(` cd ${path7.relative(process.cwd(), targetPath) || "."}`);
72729
+ console.error(` cd ${path9.relative(process.cwd(), targetPath) || "."}`);
72730
+ console.error(` lotics docs # the contracts, at the versions installed here`);
71274
72731
  console.error(` # edit src/App.tsx, then:`);
72732
+ console.error(` lotics app check # every pre-flight a deploy runs`);
71275
72733
  console.error(` lotics app deploy`);
71276
72734
  }
71277
72735
  async function appSetSubdomain(client, args) {
@@ -71327,11 +72785,11 @@ async function appVersions(client, args) {
71327
72785
  }
71328
72786
  }
71329
72787
  function defaultPullTarget(appId, appName) {
71330
- const cwdPkgPath = path7.join(process.cwd(), "package.json");
71331
- if (fs6.existsSync(cwdPkgPath)) {
72788
+ const cwdPkgPath = path9.join(process.cwd(), "package.json");
72789
+ if (fs8.existsSync(cwdPkgPath)) {
71332
72790
  let pkg2 = null;
71333
72791
  try {
71334
- pkg2 = JSON.parse(fs6.readFileSync(cwdPkgPath, "utf-8"));
72792
+ pkg2 = JSON.parse(fs8.readFileSync(cwdPkgPath, "utf-8"));
71335
72793
  } catch (err2) {
71336
72794
  console.error(
71337
72795
  `\u26A0 Could not parse ${cwdPkgPath} (${err2 instanceof Error ? err2.message : String(err2)}) \u2014 pulling into a fresh ${appDirName(appName)}/ subdir.`
@@ -71352,20 +72810,25 @@ async function appPull(client, args) {
71352
72810
  if (!versionId) throw new Error(`App ${app.id} has no version to pull.`);
71353
72811
  const version2 = await client.getAppVersion(app.id, versionId);
71354
72812
  const sourceUrl = await client.getAppVersionSourceUrl(app.id, version2.id);
71355
- const targetPath = path7.resolve(args.targetPath ?? defaultPullTarget(app.id, app.name));
71356
- fs6.mkdirSync(targetPath, { recursive: true });
71357
- const tmpFile = path7.join(tmpdir(), `lotics-app-${app.id}-${Date.now()}.tar.gz`);
72813
+ const targetPath = path9.resolve(args.targetPath ?? defaultPullTarget(app.id, app.name));
72814
+ fs8.mkdirSync(targetPath, { recursive: true });
72815
+ const tmpFile = path9.join(tmpdir(), `lotics-app-${app.id}-${Date.now()}.tar.gz`);
71358
72816
  console.error(`Downloading source archive...`);
71359
72817
  await downloadToFile(sourceUrl, tmpFile);
71360
72818
  const keptLocal = [];
71361
- const stagingDir = fs6.mkdtempSync(path7.join(tmpdir(), `lotics-pull-${app.id}-`));
72819
+ const stagingDir = fs8.mkdtempSync(path9.join(tmpdir(), `lotics-pull-${app.id}-`));
71362
72820
  try {
71363
72821
  await runTar(["-xzf", tmpFile, "-C", stagingDir], stagingDir);
71364
72822
  console.error(`Extracting to ${targetPath}...`);
71365
- keptLocal.push(...copyTree(stagingDir, targetPath, { overwrite: args.force === true }));
72823
+ keptLocal.push(
72824
+ ...copyTree(stagingDir, targetPath, {
72825
+ overwrite: args.force === true,
72826
+ skip: [WORKFLOWS_DIR, AGENTS_DIR].map((d) => d.split(path9.sep).join("/"))
72827
+ })
72828
+ );
71366
72829
  } finally {
71367
- if (fs6.existsSync(tmpFile)) fs6.unlinkSync(tmpFile);
71368
- fs6.rmSync(stagingDir, { recursive: true, force: true });
72830
+ if (fs8.existsSync(tmpFile)) fs8.unlinkSync(tmpFile);
72831
+ fs8.rmSync(stagingDir, { recursive: true, force: true });
71369
72832
  }
71370
72833
  stampPulledManifest(targetPath, {
71371
72834
  app_id: app.id,
@@ -71425,20 +72888,20 @@ async function appPull(client, args) {
71425
72888
  reportKeptLocalFiles(keptLocal, app.id);
71426
72889
  console.error(`
71427
72890
  Ready. Next steps:`);
71428
- console.error(` cd ${path7.relative(process.cwd(), targetPath) || "."}`);
72891
+ console.error(` cd ${path9.relative(process.cwd(), targetPath) || "."}`);
71429
72892
  console.error(` # edit src/App.tsx`);
71430
72893
  console.error(` lotics app deploy`);
71431
72894
  }
71432
72895
  function readAppSourceText(projectDir) {
71433
- const srcDir = path7.join(projectDir, "src");
71434
- if (!fs6.existsSync(srcDir)) return "";
72896
+ const srcDir = path9.join(projectDir, "src");
72897
+ if (!fs8.existsSync(srcDir)) return "";
71435
72898
  const parts = [];
71436
72899
  const walk2 = (dir) => {
71437
- for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
71438
- const full = path7.join(dir, entry.name);
72900
+ for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
72901
+ const full = path9.join(dir, entry.name);
71439
72902
  if (entry.isDirectory()) walk2(full);
71440
- else if (isAppSourcePath(path7.relative(projectDir, full))) {
71441
- parts.push(fs6.readFileSync(full, "utf8"));
72903
+ else if (isAppSourcePath(path9.relative(projectDir, full))) {
72904
+ parts.push(fs8.readFileSync(full, "utf8"));
71442
72905
  }
71443
72906
  }
71444
72907
  };
@@ -71446,7 +72909,7 @@ function readAppSourceText(projectDir) {
71446
72909
  return parts.join("\n");
71447
72910
  }
71448
72911
  async function appDeploy(client, args) {
71449
- const projectDir = path7.resolve(args.projectDir ?? process.cwd());
72912
+ const projectDir = path9.resolve(args.projectDir ?? process.cwd());
71450
72913
  const meta3 = readAppMeta(projectDir);
71451
72914
  warnAboutDevLink(projectDir, "deploy");
71452
72915
  const liveApp = await client.getApp(meta3.app_id);
@@ -71463,6 +72926,7 @@ async function appDeploy(client, args) {
71463
72926
  const called = calledAppAliases(sourceText);
71464
72927
  warnIfDynamicAliases(called);
71465
72928
  warnIfUndeclaredCapabilities(sourceText, meta3.capabilities);
72929
+ warnIfProjectFootguns(projectDir, sourceText);
71466
72930
  writeAppDts(projectDir, { workflows: meta3.workflows, queries: meta3.queries, agents: meta3.agents });
71467
72931
  try {
71468
72932
  await writeGeneratedAppFields(
@@ -71479,14 +72943,14 @@ async function appDeploy(client, args) {
71479
72943
  }
71480
72944
  console.error("Building...");
71481
72945
  await runNpm(["run", "build"], projectDir);
71482
- const distDir = path7.join(projectDir, "dist");
71483
- if (!fs6.existsSync(distDir)) {
72946
+ const distDir = path9.join(projectDir, "dist");
72947
+ if (!fs8.existsSync(distDir)) {
71484
72948
  throw new Error(
71485
72949
  `Build did not produce a dist/ directory in ${projectDir}. Check that 'npm run build' is configured correctly (Vite/etc. defaults to dist/).`
71486
72950
  );
71487
72951
  }
71488
- const tmpSource = path7.join(tmpdir(), `lotics-source-${Date.now()}.tar.gz`);
71489
- const tmpDist = path7.join(tmpdir(), `lotics-dist-${Date.now()}.tar.gz`);
72952
+ const tmpSource = path9.join(tmpdir(), `lotics-source-${Date.now()}.tar.gz`);
72953
+ const tmpDist = path9.join(tmpdir(), `lotics-dist-${Date.now()}.tar.gz`);
71490
72954
  try {
71491
72955
  console.error("Packaging source...");
71492
72956
  await runTar(
@@ -71510,8 +72974,8 @@ async function appDeploy(client, args) {
71510
72974
  const result = await client.deployAppVersion({
71511
72975
  queries: liveQueries,
71512
72976
  app_id: meta3.app_id,
71513
- source_archive: fs6.readFileSync(tmpSource),
71514
- dist_archive: fs6.readFileSync(tmpDist),
72977
+ source_archive: fs8.readFileSync(tmpSource),
72978
+ dist_archive: fs8.readFileSync(tmpDist),
71515
72979
  prev_version_id: meta3.current_version_id,
71516
72980
  message: args.message ?? derivedDeployMessage(pending),
71517
72981
  // Capabilities ARE manifest-authoritative: always send,
@@ -71555,6 +73019,7 @@ async function appDeploy(client, args) {
71555
73019
  if (liveAfter) {
71556
73020
  warnIfUnbranded(liveAfter);
71557
73021
  warnIfUnboundAliases(liveAfter, called);
73022
+ warnIfInertAgentTools(liveAfter);
71558
73023
  }
71559
73024
  if (liveAfter) {
71560
73025
  await pruneOrphanedBindings(
@@ -71575,8 +73040,8 @@ async function appDeploy(client, args) {
71575
73040
  throw err2;
71576
73041
  }
71577
73042
  } finally {
71578
- if (fs6.existsSync(tmpSource)) fs6.unlinkSync(tmpSource);
71579
- if (fs6.existsSync(tmpDist)) fs6.unlinkSync(tmpDist);
73043
+ if (fs8.existsSync(tmpSource)) fs8.unlinkSync(tmpSource);
73044
+ if (fs8.existsSync(tmpDist)) fs8.unlinkSync(tmpDist);
71580
73045
  }
71581
73046
  }
71582
73047
  function warnIfUnbranded(app) {
@@ -71603,8 +73068,82 @@ async function readLiveQueries(client, appId) {
71603
73068
  process.exit(1);
71604
73069
  }
71605
73070
  }
73071
+ async function installCommand(client, args) {
73072
+ const res = await client.installPackage(args.packageId, {});
73073
+ if (res.kind === "app") {
73074
+ const { app } = res;
73075
+ console.error(
73076
+ `Installed "${app.name}" (${app.id})` + (app.package_version !== null ? ` at v${app.package_version}` : "") + `.
73077
+ Open it from the workspace's app launcher.
73078
+ To work on it locally: lotics app pull ${app.id}
73079
+ To give it an address: lotics app subdomain <slug> (from inside that project)`
73080
+ );
73081
+ reportKnowledgeWarnings(res.knowledge_warnings);
73082
+ return;
73083
+ }
73084
+ console.error(
73085
+ `Installed content package (${res.installation.id}).
73086
+ Its documents and templates are now in this workspace.`
73087
+ );
73088
+ reportKnowledgeWarnings(res.warnings);
73089
+ }
73090
+ function reportKnowledgeWarnings(warnings) {
73091
+ const missing = warnings.missing_expected_docs;
73092
+ if (missing.length === 0) return;
73093
+ console.error(
73094
+ `
73095
+ \u26A0 ${missing.length} expected knowledge doc(s) are missing from this workspace:
73096
+ ` + missing.map((name2) => ` \u2022 ${name2}`).join("\n") + `
73097
+ An agent declaring one of these reads nothing from it \u2014 the run still completes,
73098
+ answering from nowhere. Create them, then re-run the install.`
73099
+ );
73100
+ }
73101
+ async function upgradeCommand(client, args) {
73102
+ const projectDir = path9.resolve(args.projectDir ?? process.cwd());
73103
+ const meta3 = readAppMeta(projectDir);
73104
+ const plan = await client.previewPackageUpgrade(meta3.app_id, {});
73105
+ if (plan.from_version === plan.to_version) {
73106
+ console.error(`Already on v${plan.from_version} \u2014 nothing to take.`);
73107
+ return;
73108
+ }
73109
+ const blockers = [
73110
+ ...plan.diff.breaking.map(
73111
+ (b) => ` \u2022 breaking: ${b.entity}.${b.alias} ${b.kind} ${b.from} \u2192 ${b.to}`
73112
+ ),
73113
+ ...plan.modified.map(
73114
+ (m) => ` \u2022 locally modified: ${m.kind} "${m.alias}"` + (m.baseline_unknown === true ? " (no baseline recorded \u2014 cannot tell what changed)" : "")
73115
+ ),
73116
+ ...plan.drift.map((d) => ` \u2022 drifted: ${d.namespace} "${d.alias}"`),
73117
+ // The FOURTH refusal source, and the one this originally missed. The server
73118
+ // throws a BadRequestError for every entry `knowledgeEntryNeedsConsent`
73119
+ // accepts, so leaving it out did not make those upgrades work — it swapped
73120
+ // the carefully-framed refusal for a raw wire 400. Read the canonical
73121
+ // predicate rather than re-deriving the rule: a hand-listed subset of a
73122
+ // server-side decision is how three of four got covered in the first place.
73123
+ ...plan.knowledge.filter(knowledgeEntryNeedsConsent).map(
73124
+ (k) => ` \u2022 knowledge: "${k.alias}" (${k.change}${k.modified ? ", locally edited" : ""})`
73125
+ )
73126
+ ];
73127
+ if (blockers.length > 0) {
73128
+ console.error(
73129
+ `v${plan.from_version} \u2192 v${plan.to_version} needs decisions this command will not make for you:
73130
+ ` + blockers.join("\n") + `
73131
+
73132
+ Each one resolves by choosing what to KEEP, and guessing would discard
73133
+ work nobody asked to lose. Resolve them, or ask an operator to run the
73134
+ upgrade with explicit resolutions.`
73135
+ );
73136
+ process.exit(1);
73137
+ }
73138
+ const applied = await client.upgradePackage(meta3.app_id, { version: plan.to_version });
73139
+ console.error(
73140
+ `Upgraded to v${applied.package_version ?? plan.to_version}.` + (plan.changelog ? `
73141
+ ${plan.changelog}` : "") + `
73142
+ Run \`lotics app pull\` to bring this checkout in step.`
73143
+ );
73144
+ }
71606
73145
  async function appCheck(client, args = {}) {
71607
- const projectDir = path7.resolve(args.projectDir ?? process.cwd());
73146
+ const projectDir = path9.resolve(args.projectDir ?? process.cwd());
71608
73147
  const meta3 = readAppMeta(projectDir);
71609
73148
  const app = await client.getApp(meta3.app_id);
71610
73149
  const sourceText = readAppSourceText(projectDir);
@@ -71621,6 +73160,8 @@ async function appCheck(client, args = {}) {
71621
73160
  warnIfDynamicAliases(called);
71622
73161
  warnIfUndeclaredCapabilities(sourceText, meta3.capabilities);
71623
73162
  warnIfUnboundAliases(app, called);
73163
+ warnIfInertAgentTools(app);
73164
+ warnIfProjectFootguns(projectDir, sourceText);
71624
73165
  warnIfUnbranded(app);
71625
73166
  if (!nothingPending(pending)) {
71626
73167
  process.exit(1);
@@ -71708,8 +73249,8 @@ function driftedAgentAliases(projectDir, liveAgents) {
71708
73249
  const drifted = [];
71709
73250
  for (const [alias, live] of Object.entries(liveAgents ?? {})) {
71710
73251
  const file2 = agentFilePath2(projectDir, alias);
71711
- if (!fs6.existsSync(file2)) continue;
71712
- const local = stripAgentHeader(fs6.readFileSync(file2, "utf-8"));
73252
+ if (!fs8.existsSync(file2)) continue;
73253
+ const local = stripAgentHeader(fs8.readFileSync(file2, "utf-8"));
71713
73254
  if (local === "" || local === (live.instructions ?? "")) continue;
71714
73255
  drifted.push(alias);
71715
73256
  }
@@ -71812,21 +73353,22 @@ function derivedDeployMessage(pending) {
71812
73353
  }
71813
73354
  function copyTree(from, to, opts, relative = "") {
71814
73355
  const kept = [];
71815
- for (const entry of fs6.readdirSync(from, { withFileTypes: true })) {
71816
- const src = path7.join(from, entry.name);
71817
- const dest = path7.join(to, entry.name);
71818
- const rel = path7.join(relative, entry.name);
73356
+ for (const entry of fs8.readdirSync(from, { withFileTypes: true })) {
73357
+ const src = path9.join(from, entry.name);
73358
+ const dest = path9.join(to, entry.name);
73359
+ const rel = path9.join(relative, entry.name);
73360
+ if (opts.skip?.includes(rel.split(path9.sep).join("/"))) continue;
71819
73361
  if (entry.isDirectory()) {
71820
- fs6.mkdirSync(dest, { recursive: true });
73362
+ fs8.mkdirSync(dest, { recursive: true });
71821
73363
  kept.push(...copyTree(src, dest, opts, rel));
71822
73364
  continue;
71823
73365
  }
71824
- if (!opts.overwrite && fs6.existsSync(dest)) {
71825
- if (fs6.readFileSync(src).equals(fs6.readFileSync(dest))) continue;
73366
+ if (!opts.overwrite && fs8.existsSync(dest)) {
73367
+ if (fs8.readFileSync(src).equals(fs8.readFileSync(dest))) continue;
71826
73368
  kept.push(rel);
71827
73369
  continue;
71828
73370
  }
71829
- fs6.copyFileSync(src, dest);
73371
+ fs8.copyFileSync(src, dest);
71830
73372
  }
71831
73373
  return kept;
71832
73374
  }
@@ -71862,6 +73404,151 @@ function warnIfUndeclaredCapabilities(sourceText, capabilities) {
71862
73404
  Add to package.json#lotics.capabilities: ${block}`
71863
73405
  );
71864
73406
  }
73407
+ var VITE_CONFIG_NAMES = [
73408
+ "vite.config.ts",
73409
+ "vite.config.mts",
73410
+ "vite.config.cts",
73411
+ "vite.config.js",
73412
+ "vite.config.mjs",
73413
+ "vite.config.cjs"
73414
+ ];
73415
+ function codeOnly(source) {
73416
+ let out = "";
73417
+ let i2 = 0;
73418
+ while (i2 < source.length) {
73419
+ const two = source.slice(i2, i2 + 2);
73420
+ if (two === "//") {
73421
+ while (i2 < source.length && source[i2] !== "\n") {
73422
+ out += " ";
73423
+ i2++;
73424
+ }
73425
+ continue;
73426
+ }
73427
+ if (two === "/*") {
73428
+ while (i2 < source.length && source.slice(i2, i2 + 2) !== "*/") {
73429
+ out += source[i2] === "\n" ? "\n" : " ";
73430
+ i2++;
73431
+ }
73432
+ out += " ";
73433
+ i2 += 2;
73434
+ continue;
73435
+ }
73436
+ const quote = source[i2];
73437
+ if (quote === '"' || quote === "'" || quote === "`") {
73438
+ out += " ";
73439
+ i2++;
73440
+ while (i2 < source.length && source[i2] !== quote) {
73441
+ if (source[i2] === "\\") {
73442
+ out += " ";
73443
+ i2 += 2;
73444
+ continue;
73445
+ }
73446
+ out += source[i2] === "\n" ? "\n" : " ";
73447
+ i2++;
73448
+ }
73449
+ out += " ";
73450
+ i2++;
73451
+ continue;
73452
+ }
73453
+ out += source[i2];
73454
+ i2++;
73455
+ }
73456
+ return out;
73457
+ }
73458
+ function defineBlockKeys(config2) {
73459
+ const code = codeOnly(config2);
73460
+ const keys3 = /* @__PURE__ */ new Set();
73461
+ for (const opener of code.matchAll(/\bdefine\s*:\s*\{/g)) {
73462
+ let depth = 1;
73463
+ let i2 = opener.index + opener[0].length;
73464
+ const start = i2;
73465
+ while (i2 < code.length && depth > 0) {
73466
+ if (code[i2] === "{") depth++;
73467
+ else if (code[i2] === "}") depth--;
73468
+ i2++;
73469
+ }
73470
+ for (const key of code.slice(start, i2).matchAll(/(?:^|[{,])\s*([A-Za-z_$][\w$]*)\s*:/g)) {
73471
+ keys3.add(key[1]);
73472
+ }
73473
+ }
73474
+ return keys3;
73475
+ }
73476
+ function warnIfProjectFootguns(projectDir, sourceText) {
73477
+ const lines = [];
73478
+ const configName = VITE_CONFIG_NAMES.find((name2) => fs8.existsSync(path9.join(projectDir, name2)));
73479
+ if (configName !== void 0) {
73480
+ const config2 = fs8.readFileSync(path9.join(projectDir, configName), "utf-8");
73481
+ const defined = defineBlockKeys(config2);
73482
+ const missing = ["global", "__DEV__"].filter((name2) => !defined.has(name2));
73483
+ if (missing.length > 0) {
73484
+ lines.push(
73485
+ [
73486
+ ` \u2022 ${configName} does not define ${missing.join(" or ")} \u2014 react-native-web reads`,
73487
+ " `global.cancelAnimationFrame` and `__DEV__` as free variables, so the DEPLOYED",
73488
+ " app throws while dev, build and typecheck all pass.",
73489
+ ' define: { __DEV__: "false", global: "globalThis" } (top level AND optimizeDeps.esbuildOptions)'
73490
+ ].join("\n")
73491
+ );
73492
+ }
73493
+ }
73494
+ if (/\bwindow\.open\s*\(/.test(sourceText)) {
73495
+ lines.push(
73496
+ [
73497
+ " \u2022 src calls window.open \u2014 the app runs in a sandboxed iframe, which drops it",
73498
+ " silently: no error, no navigation.",
73499
+ ' import { openExternal } from "@lotics/app-sdk"'
73500
+ ].join("\n")
73501
+ );
73502
+ }
73503
+ if (lines.length === 0) return;
73504
+ console.error(
73505
+ ["\n\u26A0 This project has a footgun that only shows up once deployed:", ...lines].join("\n")
73506
+ );
73507
+ }
73508
+ var DECLARATION_BOUND_TOOLS = [
73509
+ { tool: "run_app_query", field: "query_aliases", noun: "read nothing", placeholder: "<alias>" },
73510
+ { tool: "run_app_workflow", field: "workflow_aliases", noun: "write nothing", placeholder: "<alias>" },
73511
+ { tool: "grep_knowledge", field: "knowledge_doc_ids", noun: "search nothing", placeholder: "kdc_\u2026" },
73512
+ { tool: "read_knowledge", field: "knowledge_doc_ids", noun: "read nothing", placeholder: "kdc_\u2026" },
73513
+ { tool: "list_knowledge", field: "knowledge_doc_ids", noun: "list nothing", placeholder: "kdc_\u2026" }
73514
+ ];
73515
+ function warnIfInertAgentTools(app) {
73516
+ const lines = [];
73517
+ for (const [alias, agent] of Object.entries(app.agents ?? {})) {
73518
+ const decl = agent;
73519
+ const tools = decl.tool_names ?? [];
73520
+ for (const { tool, field, noun, placeholder } of DECLARATION_BOUND_TOOLS) {
73521
+ const granted = decl[field];
73522
+ const hasReach = Array.isArray(granted) && granted.length > 0;
73523
+ const hasTool = tools.includes(tool);
73524
+ if (hasTool && !hasReach) {
73525
+ lines.push(
73526
+ ` \u2022 agent "${alias}" holds ${tool} with no ${field} \u2014 it can ${noun}.
73527
+ lotics run set_app_agent '{"app_id":"${app.id}","alias":"${alias}","${field}":["${placeholder}"]}'`
73528
+ );
73529
+ }
73530
+ if (!hasTool && hasReach && !anyToolReaches(tools, field)) {
73531
+ lines.push(
73532
+ ` \u2022 agent "${alias}" declares ${field} but holds no tool that reads it \u2014 nothing can reach them.
73533
+ lotics run set_app_agent '{"app_id":"${app.id}","alias":"${alias}","tool_names":${JSON.stringify([...tools, tool])}}'`
73534
+ );
73535
+ }
73536
+ }
73537
+ }
73538
+ if (lines.length === 0) return;
73539
+ console.error(
73540
+ [
73541
+ "\n\u26A0 An agent's capability and its reach disagree. The tool is the capability;",
73542
+ " the declared list is the reach \u2014 either half alone means the call is refused",
73543
+ " or never made, and the run still completes, so this shows up as a prompt problem.",
73544
+ ...lines,
73545
+ " Then `lotics app pull` to bring the manifest back in step."
73546
+ ].join("\n")
73547
+ );
73548
+ }
73549
+ function anyToolReaches(tools, field) {
73550
+ return DECLARATION_BOUND_TOOLS.some((g) => g.field === field && tools.includes(g.tool));
73551
+ }
71865
73552
  function warnIfUnboundAliases(app, called) {
71866
73553
  const {
71867
73554
  queries: unboundQueries,
@@ -71886,7 +73573,7 @@ function warnIfUnboundAliases(app, called) {
71886
73573
  console.error(lines.join("\n"));
71887
73574
  }
71888
73575
  async function appDev(client, args) {
71889
- const projectDir = path7.resolve(args.projectDir ?? process.cwd());
73576
+ const projectDir = path9.resolve(args.projectDir ?? process.cwd());
71890
73577
  const meta3 = readAppMeta(projectDir);
71891
73578
  warnAboutDevLink(projectDir, "dev");
71892
73579
  writeAppDts(projectDir, { workflows: meta3.workflows, queries: meta3.queries, agents: meta3.agents });
@@ -72115,7 +73802,7 @@ async function appAgentSet(client, args) {
72115
73802
  const declared = meta3.agents?.[args.alias];
72116
73803
  const hasDeclaration = declared?.inputs !== void 0 || declared?.outputs !== void 0;
72117
73804
  const file2 = agentFilePath2(projectDir, args.alias);
72118
- const hasProse = fs6.existsSync(file2);
73805
+ const hasProse = fs8.existsSync(file2);
72119
73806
  if (!hasProse) {
72120
73807
  if (isNew) {
72121
73808
  console.error(
@@ -72126,16 +73813,16 @@ async function appAgentSet(client, args) {
72126
73813
  }
72127
73814
  if (!hasDeclaration) {
72128
73815
  console.error(
72129
- `No instructions at ${path7.relative(projectDir, file2)}, and nothing declared in package.json#lotics.agents.${args.alias} to push instead.
73816
+ `No instructions at ${path9.relative(projectDir, file2)}, and nothing declared in package.json#lotics.agents.${args.alias} to push instead.
72130
73817
  Run 'lotics app pull ${meta3.app_id}' to write ${AGENTS_DIR}/${args.alias}.md, then edit it.`
72131
73818
  );
72132
73819
  process.exit(1);
72133
73820
  }
72134
73821
  }
72135
- const instructions = hasProse ? stripAgentHeader(fs6.readFileSync(file2, "utf-8")) : void 0;
73822
+ const instructions = hasProse ? stripAgentHeader(fs8.readFileSync(file2, "utf-8")) : void 0;
72136
73823
  if (instructions === "") {
72137
73824
  console.error(
72138
- `${path7.relative(projectDir, file2)} is empty after stripping the header \u2014 refusing to push an empty prompt.`
73825
+ `${path9.relative(projectDir, file2)} is empty after stripping the header \u2014 refusing to push an empty prompt.`
72139
73826
  );
72140
73827
  process.exit(1);
72141
73828
  }
@@ -72167,6 +73854,27 @@ async function appAgentSet(client, args) {
72167
73854
  // The verb is the one line that says which of the two just happened.
72168
73855
  `${isNew ? "Created" : "Set"} agent "${args.alias}" (${pushed.join(", ")}` + (live?.model_tier ? `, ${live.model_tier}` : "") + (isNew ? `). Nothing else is declared yet \u2014 set tool_names / knowledge_doc_ids / query_aliases with set_app_agent.` : `). Everything else left as it is.`)
72169
73856
  );
73857
+ const UNSENT = [
73858
+ "tool_names",
73859
+ "query_aliases",
73860
+ "workflow_aliases",
73861
+ "knowledge_doc_ids",
73862
+ "model_tier",
73863
+ "effort_level"
73864
+ ];
73865
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
73866
+ const liveFields = { ...live };
73867
+ const carried = UNSENT.filter(
73868
+ (f) => declared?.[f] !== void 0 && !same(declared[f], liveFields[f])
73869
+ );
73870
+ if (!isNew && carried.length > 0) {
73871
+ console.error(
73872
+ ` Not sent: ${carried.join(", ")} \u2014 this command pushes prose, inputs and outputs only.
73873
+ Edit one of those in package.json and nothing moves. To change it:
73874
+ lotics run set_app_agent '{"app_id":"${meta3.app_id}","alias":"${args.alias}","<field>":\u2026}'
73875
+ then \`lotics app pull\`.`
73876
+ );
73877
+ }
72170
73878
  }
72171
73879
  async function appWorkflowSet(client, args) {
72172
73880
  const projectDir = args.projectDir ?? process.cwd();
@@ -72179,18 +73887,18 @@ async function appWorkflowSet(client, args) {
72179
73887
  process.exit(1);
72180
73888
  }
72181
73889
  const file2 = workflowFilePath(projectDir, args.alias);
72182
- if (!fs6.existsSync(file2)) {
73890
+ if (!fs8.existsSync(file2)) {
72183
73891
  console.error(
72184
- `No workflow body at ${path7.relative(projectDir, file2)}.
73892
+ `No workflow body at ${path9.relative(projectDir, file2)}.
72185
73893
  If "${args.alias}" is already bound: lotics app pull ${meta3.app_id} (writes the body)
72186
73894
  If it is a NEW alias you declared: create that file yourself, then re-run this.`
72187
73895
  );
72188
73896
  process.exit(1);
72189
73897
  }
72190
73898
  noteScope({ alias: args.alias });
72191
- const source = stripWorkflowHeader(fs6.readFileSync(file2, "utf-8"));
73899
+ const source = stripWorkflowHeader(fs8.readFileSync(file2, "utf-8"));
72192
73900
  if (source.trim() === "") {
72193
- console.error(`Workflow body ${path7.relative(projectDir, file2)} is empty after stripping the header.`);
73901
+ console.error(`Workflow body ${path9.relative(projectDir, file2)} is empty after stripping the header.`);
72194
73902
  process.exit(1);
72195
73903
  }
72196
73904
  const res = await client.setAppWorkflow(meta3.app_id, args.alias, {
@@ -72323,15 +74031,15 @@ async function appWorkflowCheck(args) {
72323
74031
  for (const alias of aliases) {
72324
74032
  const bodyPath = workflowFilePath(projectDir, alias);
72325
74033
  const globalsPath = workflowGlobalsPath(projectDir, alias);
72326
- if (!fs6.existsSync(bodyPath)) {
74034
+ if (!fs8.existsSync(bodyPath)) {
72327
74035
  console.error(
72328
- `\u26A0 Skipped "${alias}" \u2014 no body at ${path7.relative(projectDir, bodyPath)}. Run 'lotics app workflow pull' to write it.`
74036
+ `\u26A0 Skipped "${alias}" \u2014 no body at ${path9.relative(projectDir, bodyPath)}. Run 'lotics app workflow pull' to write it.`
72329
74037
  );
72330
74038
  continue;
72331
74039
  }
72332
- if (!fs6.existsSync(globalsPath)) {
74040
+ if (!fs8.existsSync(globalsPath)) {
72333
74041
  console.error(
72334
- `Cannot check "${alias}" \u2014 missing types at ${path7.relative(projectDir, globalsPath)}. Run 'lotics app workflow pull' (or 'lotics app codegen') to fetch them.`
74042
+ `Cannot check "${alias}" \u2014 missing types at ${path9.relative(projectDir, globalsPath)}. Run 'lotics app workflow pull' (or 'lotics app codegen') to fetch them.`
72335
74043
  );
72336
74044
  process.exit(1);
72337
74045
  }
@@ -72350,7 +74058,7 @@ async function appWorkflowCheck(args) {
72350
74058
  function printWorkflowCheckResults(projectDir, results) {
72351
74059
  let totalErrors = 0;
72352
74060
  for (const r of results) {
72353
- const rel = path7.relative(projectDir, r.bodyPath);
74061
+ const rel = path9.relative(projectDir, r.bodyPath);
72354
74062
  if (r.issues.length === 0) {
72355
74063
  console.error(`\u2713 ${r.alias} (${rel}) \u2014 clean`);
72356
74064
  continue;
@@ -72562,11 +74270,11 @@ async function validateOrgWorkspacePin(client, orgId, profile) {
72562
74270
  }
72563
74271
 
72564
74272
  // src/xlsx.ts
72565
- import fs8 from "node:fs";
74273
+ import fs10 from "node:fs";
72566
74274
 
72567
74275
  // src/file_command_io.ts
72568
- import fs7 from "node:fs";
72569
- import path8 from "node:path";
74276
+ import fs9 from "node:fs";
74277
+ import path10 from "node:path";
72570
74278
  var CliError = class extends Error {
72571
74279
  constructor(message2) {
72572
74280
  super(message2);
@@ -72587,14 +74295,14 @@ function rejectExtraArgs(rest2, arity, usage) {
72587
74295
  fail2(`${usage} takes ${arity} argument${arity === 1 ? "" : "s"} after <file>, got ${rest2.length}. Unused: ${rest2.slice(arity).join(" ")}`);
72588
74296
  }
72589
74297
  function writeFileAtomic(filePath, bytes) {
72590
- const dir = path8.dirname(path8.resolve(filePath));
72591
- const tmp = path8.join(dir, `.${path8.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
72592
- fs7.writeFileSync(tmp, bytes);
74298
+ const dir = path10.dirname(path10.resolve(filePath));
74299
+ const tmp = path10.join(dir, `.${path10.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
74300
+ fs9.writeFileSync(tmp, bytes);
72593
74301
  try {
72594
- fs7.renameSync(tmp, filePath);
74302
+ fs9.renameSync(tmp, filePath);
72595
74303
  } catch (error52) {
72596
74304
  try {
72597
- fs7.unlinkSync(tmp);
74305
+ fs9.unlinkSync(tmp);
72598
74306
  } catch {
72599
74307
  }
72600
74308
  throw error52;
@@ -75391,16 +77099,16 @@ var MatcherView = class {
75391
77099
  * @returns {string|undefined}
75392
77100
  */
75393
77101
  getCurrentTag() {
75394
- const path10 = this._matcher.path;
75395
- return path10.length > 0 ? path10[path10.length - 1].tag : void 0;
77102
+ const path12 = this._matcher.path;
77103
+ return path12.length > 0 ? path12[path12.length - 1].tag : void 0;
75396
77104
  }
75397
77105
  /**
75398
77106
  * Get current namespace.
75399
77107
  * @returns {string|undefined}
75400
77108
  */
75401
77109
  getCurrentNamespace() {
75402
- const path10 = this._matcher.path;
75403
- return path10.length > 0 ? path10[path10.length - 1].namespace : void 0;
77110
+ const path12 = this._matcher.path;
77111
+ return path12.length > 0 ? path12[path12.length - 1].namespace : void 0;
75404
77112
  }
75405
77113
  /**
75406
77114
  * Get current node's attribute value.
@@ -75408,9 +77116,9 @@ var MatcherView = class {
75408
77116
  * @returns {*}
75409
77117
  */
75410
77118
  getAttrValue(attrName) {
75411
- const path10 = this._matcher.path;
75412
- if (path10.length === 0) return void 0;
75413
- return path10[path10.length - 1].values?.[attrName];
77119
+ const path12 = this._matcher.path;
77120
+ if (path12.length === 0) return void 0;
77121
+ return path12[path12.length - 1].values?.[attrName];
75414
77122
  }
75415
77123
  /**
75416
77124
  * Check if current node has an attribute.
@@ -75418,9 +77126,9 @@ var MatcherView = class {
75418
77126
  * @returns {boolean}
75419
77127
  */
75420
77128
  hasAttr(attrName) {
75421
- const path10 = this._matcher.path;
75422
- if (path10.length === 0) return false;
75423
- const current = path10[path10.length - 1];
77129
+ const path12 = this._matcher.path;
77130
+ if (path12.length === 0) return false;
77131
+ const current = path12[path12.length - 1];
75424
77132
  return current.values !== void 0 && attrName in current.values;
75425
77133
  }
75426
77134
  /**
@@ -75446,18 +77154,18 @@ var MatcherView = class {
75446
77154
  * @returns {number}
75447
77155
  */
75448
77156
  getPosition() {
75449
- const path10 = this._matcher.path;
75450
- if (path10.length === 0) return -1;
75451
- return path10[path10.length - 1].position ?? 0;
77157
+ const path12 = this._matcher.path;
77158
+ if (path12.length === 0) return -1;
77159
+ return path12[path12.length - 1].position ?? 0;
75452
77160
  }
75453
77161
  /**
75454
77162
  * Get current node's repeat counter (occurrence count of this tag name).
75455
77163
  * @returns {number}
75456
77164
  */
75457
77165
  getCounter() {
75458
- const path10 = this._matcher.path;
75459
- if (path10.length === 0) return -1;
75460
- return path10[path10.length - 1].counter ?? 0;
77166
+ const path12 = this._matcher.path;
77167
+ if (path12.length === 0) return -1;
77168
+ return path12[path12.length - 1].counter ?? 0;
75461
77169
  }
75462
77170
  /**
75463
77171
  * Get current node's sibling index (alias for getPosition).
@@ -81115,10 +82823,10 @@ function resolveRelativePath(basePath, relativePath) {
81115
82823
  }
81116
82824
  return resolved.join("/");
81117
82825
  }
81118
- function findZipEntry(zipEntries, path10) {
81119
- if (zipEntries[path10]) return zipEntries[path10];
81120
- if (zipEntries[`xl/${path10}`]) return zipEntries[`xl/${path10}`];
81121
- const noSlash = path10.replace(/^\//, "");
82826
+ function findZipEntry(zipEntries, path12) {
82827
+ if (zipEntries[path12]) return zipEntries[path12];
82828
+ if (zipEntries[`xl/${path12}`]) return zipEntries[`xl/${path12}`];
82829
+ const noSlash = path12.replace(/^\//, "");
81122
82830
  if (zipEntries[noSlash]) return zipEntries[noSlash];
81123
82831
  return void 0;
81124
82832
  }
@@ -82750,9 +84458,9 @@ function parseExcelFromZip(zip, options) {
82750
84458
  }
82751
84459
  return result;
82752
84460
  }
82753
- function findEntry(zip, path10) {
82754
- if (zip[path10]) return zip[path10];
82755
- const lower2 = path10.toLowerCase();
84461
+ function findEntry(zip, path12) {
84462
+ if (zip[path12]) return zip[path12];
84463
+ const lower2 = path12.toLowerCase();
82756
84464
  for (const key of Object.keys(zip)) {
82757
84465
  if (key.toLowerCase() === lower2) return zip[key];
82758
84466
  }
@@ -83389,12 +85097,12 @@ function extractPivotRoundTripInfo(originalZip) {
83389
85097
  if (paths.length > 0) pivotTablesBySheetIndex.set(i2, paths);
83390
85098
  }
83391
85099
  const pivotXmlPaths = [];
83392
- for (const path10 of Object.keys(originalZip)) {
83393
- if (path10.startsWith("xl/pivotTables/") && path10.endsWith(".xml")) {
83394
- pivotXmlPaths.push(path10);
85100
+ for (const path12 of Object.keys(originalZip)) {
85101
+ if (path12.startsWith("xl/pivotTables/") && path12.endsWith(".xml")) {
85102
+ pivotXmlPaths.push(path12);
83395
85103
  }
83396
- if (path10.startsWith("xl/pivotCache/") && path10.endsWith(".xml")) {
83397
- pivotXmlPaths.push(path10);
85104
+ if (path12.startsWith("xl/pivotCache/") && path12.endsWith(".xml")) {
85105
+ pivotXmlPaths.push(path12);
83398
85106
  }
83399
85107
  }
83400
85108
  return { cachesByWorkbook, pivotTablesBySheetIndex, pivotXmlPaths };
@@ -83402,15 +85110,15 @@ function extractPivotRoundTripInfo(originalZip) {
83402
85110
  function decode3(bytes) {
83403
85111
  return new TextDecoder().decode(bytes);
83404
85112
  }
83405
- function pivotContentTypeFor(path10) {
83406
- if (path10.startsWith("xl/pivotTables/") && path10.endsWith(".xml")) {
83407
- return `<Override PartName="/${path10}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"/>`;
85113
+ function pivotContentTypeFor(path12) {
85114
+ if (path12.startsWith("xl/pivotTables/") && path12.endsWith(".xml")) {
85115
+ return `<Override PartName="/${path12}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"/>`;
83408
85116
  }
83409
- if (path10.includes("/pivotCacheDefinition") && path10.endsWith(".xml")) {
83410
- return `<Override PartName="/${path10}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"/>`;
85117
+ if (path12.includes("/pivotCacheDefinition") && path12.endsWith(".xml")) {
85118
+ return `<Override PartName="/${path12}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"/>`;
83411
85119
  }
83412
- if (path10.includes("/pivotCacheRecords") && path10.endsWith(".xml")) {
83413
- return `<Override PartName="/${path10}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"/>`;
85120
+ if (path12.includes("/pivotCacheRecords") && path12.endsWith(".xml")) {
85121
+ return `<Override PartName="/${path12}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"/>`;
83414
85122
  }
83415
85123
  return void 0;
83416
85124
  }
@@ -83483,16 +85191,16 @@ function exportWorkbook(workbook, originalZip) {
83483
85191
  regeneratedPaths.add(`xl/worksheets/sheet${i2 + 1}.xml`);
83484
85192
  regeneratedPaths.add(`xl/worksheets/_rels/sheet${i2 + 1}.xml.rels`);
83485
85193
  }
83486
- for (const path10 of Object.keys(originalZip)) {
83487
- if (path10.startsWith("xl/drawings/") || path10.startsWith("xl/charts/") || path10.startsWith("xl/tables/") || path10.startsWith("xl/media/")) {
83488
- regeneratedPaths.add(path10);
85194
+ for (const path12 of Object.keys(originalZip)) {
85195
+ if (path12.startsWith("xl/drawings/") || path12.startsWith("xl/charts/") || path12.startsWith("xl/tables/") || path12.startsWith("xl/media/")) {
85196
+ regeneratedPaths.add(path12);
83489
85197
  }
83490
85198
  }
83491
- for (const [path10, data2] of Object.entries(originalZip)) {
83492
- if (regeneratedPaths.has(path10)) continue;
83493
- const ext = path10.slice(path10.lastIndexOf(".") + 1).toLowerCase();
85199
+ for (const [path12, data2] of Object.entries(originalZip)) {
85200
+ if (regeneratedPaths.has(path12)) continue;
85201
+ const ext = path12.slice(path12.lastIndexOf(".") + 1).toLowerCase();
83494
85202
  if (!DECLARED_EXTENSIONS.includes(ext)) continue;
83495
- entries2[path10] = data2;
85203
+ entries2[path12] = data2;
83496
85204
  }
83497
85205
  }
83498
85206
  const sharedStrings = buildSharedStrings(workbook);
@@ -83508,8 +85216,8 @@ function exportWorkbook(workbook, originalZip) {
83508
85216
  buildWorkbookRels(workbook, pivotInfo)
83509
85217
  );
83510
85218
  const extraContentTypes = [];
83511
- for (const path10 of pivotInfo.pivotXmlPaths) {
83512
- const ct = pivotContentTypeFor(path10);
85219
+ for (const path12 of pivotInfo.pivotXmlPaths) {
85220
+ const ct = pivotContentTypeFor(path12);
83513
85221
  if (ct) extraContentTypes.push(ct);
83514
85222
  }
83515
85223
  let globalChartIndex = 1;
@@ -92534,14 +94242,14 @@ var DependencyGraph = class {
92534
94242
  stack.push({ cell: p, path: [cell, p] });
92535
94243
  }
92536
94244
  while (stack.length > 0) {
92537
- const { cell: current, path: path10 } = stack.pop();
92538
- if (current === cell) return path10;
94245
+ const { cell: current, path: path12 } = stack.pop();
94246
+ if (current === cell) return path12;
92539
94247
  if (visited.has(current)) continue;
92540
94248
  visited.add(current);
92541
94249
  const nextPrec = this.precedents.get(current);
92542
94250
  if (!nextPrec) continue;
92543
94251
  for (const p of nextPrec) {
92544
- stack.push({ cell: p, path: [...path10, p] });
94252
+ stack.push({ cell: p, path: [...path12, p] });
92545
94253
  }
92546
94254
  }
92547
94255
  return null;
@@ -93753,8 +95461,8 @@ var SIMPLE_SHEET_KEYS = /* @__PURE__ */ new Set(["name", "cells", "merges", "fre
93753
95461
  var MAX_COLUMN = 16384;
93754
95462
  var MAX_ROW = 1048576;
93755
95463
  function loadFile(filePath) {
93756
- if (!fs8.existsSync(filePath)) fail2(`File not found: ${filePath}`);
93757
- const buffer = fs8.readFileSync(filePath);
95464
+ if (!fs10.existsSync(filePath)) fail2(`File not found: ${filePath}`);
95465
+ const buffer = fs10.readFileSync(filePath);
93758
95466
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
93759
95467
  const originalZip = unzipXlsx(arrayBuffer);
93760
95468
  const parsed = parseExcelBuffer(arrayBuffer);
@@ -93804,7 +95512,7 @@ function lazyEngine(workbook) {
93804
95512
  };
93805
95513
  }
93806
95514
  function readToJson(filePath, filter3, opts) {
93807
- const buffer = fs8.readFileSync(filePath);
95515
+ const buffer = fs10.readFileSync(filePath);
93808
95516
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
93809
95517
  const parsed = parseExcelBuffer(arrayBuffer);
93810
95518
  let sheets = parsed.sheets;
@@ -94317,7 +96025,7 @@ async function runXlsxCommand(subcommand, toolArgs, restArgs) {
94317
96025
  }
94318
96026
 
94319
96027
  // src/docx.ts
94320
- import fs9 from "node:fs";
96028
+ import fs11 from "node:fs";
94321
96029
 
94322
96030
  // ../ooxml/src/doc/errors.ts
94323
96031
  var DocParseError = class extends Error {
@@ -98933,8 +100641,8 @@ async function docModelToDocx(model) {
98933
100641
  // ../ooxml/src/opc.ts
98934
100642
  var import_jszip = __toESM(require_lib4(), 1);
98935
100643
  function dropFolderEntries(zip) {
98936
- for (const [path10, entry] of Object.entries(zip.files)) {
98937
- if (entry.dir) delete zip.files[path10];
100644
+ for (const [path12, entry] of Object.entries(zip.files)) {
100645
+ if (entry.dir) delete zip.files[path12];
98938
100646
  }
98939
100647
  }
98940
100648
 
@@ -99166,11 +100874,11 @@ async function parseDocx(buffer) {
99166
100874
  const zip = await import_jszip3.default.loadAsync(buffer);
99167
100875
  const parts = /* @__PURE__ */ new Map();
99168
100876
  const filePromises = [];
99169
- zip.forEach((path10, file2) => {
100877
+ zip.forEach((path12, file2) => {
99170
100878
  if (file2.dir) return;
99171
100879
  filePromises.push(
99172
100880
  file2.async("uint8array").then((bytes) => {
99173
- parts.set(path10, bytes);
100881
+ parts.set(path12, bytes);
99174
100882
  })
99175
100883
  );
99176
100884
  });
@@ -99402,9 +101110,9 @@ var DEFAULT_PARTS = [
99402
101110
  ["word/theme/theme1.xml", () => encoder.encode(THEME_XML)]
99403
101111
  ];
99404
101112
  function fillDefaultParts(parts) {
99405
- for (const [path10, makeBytes] of DEFAULT_PARTS) {
99406
- if (!parts.has(path10)) {
99407
- parts.set(path10, makeBytes());
101113
+ for (const [path12, makeBytes] of DEFAULT_PARTS) {
101114
+ if (!parts.has(path12)) {
101115
+ parts.set(path12, makeBytes());
99408
101116
  }
99409
101117
  }
99410
101118
  return parts;
@@ -99414,9 +101122,9 @@ function fillDefaultParts(parts) {
99414
101122
  async function serializeDocx(doc) {
99415
101123
  const zip = new import_jszip4.default();
99416
101124
  const parts = fillDefaultParts(new Map(doc.parts));
99417
- for (const [path10, bytes] of parts) {
99418
- if (path10 === "word/document.xml") continue;
99419
- zip.file(path10, bytes);
101125
+ for (const [path12, bytes] of parts) {
101126
+ if (path12 === "word/document.xml") continue;
101127
+ zip.file(path12, bytes);
99420
101128
  }
99421
101129
  const documentXml = serializeDocumentXml(doc);
99422
101130
  zip.file("word/document.xml", documentXml);
@@ -99503,8 +101211,8 @@ var DEFAULT_DOC_ATTRS = {
99503
101211
  "@_xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
99504
101212
  };
99505
101213
  async function loadFile2(filePath) {
99506
- if (!fs9.existsSync(filePath)) fail2(`File not found: ${filePath}`);
99507
- const bytes = fs9.readFileSync(filePath);
101214
+ if (!fs11.existsSync(filePath)) fail2(`File not found: ${filePath}`);
101215
+ const bytes = fs11.readFileSync(filePath);
99508
101216
  if (isLegacyDoc(bytes)) {
99509
101217
  const upgraded = await saveDocxToBuffer(await loadDocxFromBuffer(bytes));
99510
101218
  return parseDocx(new Uint8Array(upgraded));
@@ -99989,10 +101697,10 @@ async function runDocxCommand(subcommand, toolArgs, restArgs) {
99989
101697
  }
99990
101698
 
99991
101699
  // src/knowledge.ts
99992
- import fs10 from "node:fs";
101700
+ import fs12 from "node:fs";
99993
101701
  function readBodyFile(filePath) {
99994
- if (!fs10.existsSync(filePath)) fail2(`File not found: ${filePath}`);
99995
- return fs10.readFileSync(filePath, "utf-8");
101702
+ if (!fs12.existsSync(filePath)) fail2(`File not found: ${filePath}`);
101703
+ return fs12.readFileSync(filePath, "utf-8");
99996
101704
  }
99997
101705
  function resolveBody(flags, required2) {
99998
101706
  if (flags.from !== void 0 && flags.content !== void 0) {
@@ -100136,9 +101844,9 @@ import { createServer } from "node:http";
100136
101844
  import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdtempSync, rmSync, readdirSync } from "node:fs";
100137
101845
  import { tmpdir as tmpdir2 } from "node:os";
100138
101846
  import { join as join2, dirname, resolve, extname, basename } from "node:path";
100139
- import { fileURLToPath as fileURLToPath2 } from "node:url";
101847
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
100140
101848
  import { setTimeout as sleep2 } from "node:timers/promises";
100141
- var HERE = dirname(fileURLToPath2(import.meta.url));
101849
+ var HERE = dirname(fileURLToPath3(import.meta.url));
100142
101850
  function fail3(msg) {
100143
101851
  console.error(msg);
100144
101852
  process.exit(1);
@@ -100369,84 +102077,7 @@ USAGE
100369
102077
  Query tools return IDs used as arguments to other tools.
100370
102078
 
100371
102079
  COMMANDS
100372
- lotics org List saved orgs (marks active)
100373
- lotics org use <name|id> [--local] Switch active org (--local pins this dir)
100374
- lotics workspace List workspaces in the active org (marks current)
100375
- lotics workspace select <id> Switch to a different workspace
100376
- lotics workspace create <name> Create a new workspace (admin only)
100377
- lotics workspace delete <id> --yes Delete a workspace (admin only; soft delete, recoverable)
100378
- lotics workspace doctor Report dangling schema references (admin only)
100379
- lotics tools List all available tools
100380
- lotics tools <name> Show tool description and input schema
100381
- lotics run <tool> '<json>' Execute a tool
100382
- lotics run <tool> @args.json Read JSON args from a file (large payloads)
100383
- cat args.json | lotics run <tool> Read JSON args from stdin (large payloads)
100384
- lotics app create <name> [path] Create a new custom-code app + scaffold locally
100385
- lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)
100386
- lotics app deploy -m <message> Build + upload current dir as a new version \u2014 COMMIT
100387
- (-m is required \u2014 it's the version's audit trail;
100388
- carries code + queries only \u2014 workflow bindings are
100389
- managed by set_app_workflow / remove_app_workflow)
100390
- lotics app versions [app_id] Show deploy history newest-first (version,
100391
- timestamp, deployer, build status, -m message;
100392
- * marks the currently served version)
100393
- lotics app codegen [path] Regenerate .lotics/* (types + field/option ids)
100394
- from the manifest + workspace schema \u2014 no deploy
100395
- lotics app check Run every deploy pre-flight WITHOUT building or
100396
- shipping: agent schemas vs the live app, workflow
100397
- declarations and BODIES vs what is live, aliases
100398
- the code calls but nothing bound, undeclared
100399
- capabilities, query drift. Exits 1 on what a
100400
- deploy would refuse, so CI can gate on it
100401
- lotics app workflow run <alias> '<json>' Execute a bound app workflow end-to-end
100402
- (inputs: inline JSON, @file, or stdin;
100403
- --print-created reports created records +
100404
- a paste-ready cleanup plan; --cleanup also
100405
- deletes those records \u2014 NOT a rollback)
100406
- lotics app workflow set <alias> Push the edited src/workflows/<alias>.ts body
100407
- through set_app_workflow (server verifies)
100408
- lotics app workflow pull Rewrite src/workflows/*.ts from the server
100409
- lotics app workflow check [alias] Typecheck src/workflows bodies locally (one
100410
- isolated program per alias; the app's own tsc)
100411
- lotics app query set <alias> Push package.json#lotics.queries.<alias> to
100412
- apps.queries via set_app_query (no deploy;
100413
- re-synced by the next deploy from the manifest)
100414
- lotics app agent run <app_id> <alias> '<json>' Run a bound app agent end-to-end
100415
- (inputs: inline JSON, @file, or stdin; streams
100416
- progress to stderr, reports the settled run;
100417
- --session <id> continues a thread; --json)
100418
- lotics app agent set <alias> Push the edited src/agents/<alias>.md instructions,
100419
- and ONLY those \u2014 the server merges, so the typed
100420
- fields keep whatever is bound (a manifest is a
100421
- snapshot; replaying it would revert them). Change
100422
- those with set_app_agent, then pull.
100423
- lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
100424
- lotics app rename "<new name>" Rename the app's display name (launcher title)
100425
- lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
100426
- lotics knowledge list List knowledge docs (id, name, description)
100427
- lotics knowledge create --name <n> [--description <d>] --from <file.md>
100428
- Create a doc from a file (or --content <str>);
100429
- prints the new id
100430
- lotics knowledge get <id> [-o <file.md>]
100431
- Fetch a doc's content (to a file, or stdout;
100432
- --json for the full doc)
100433
- lotics knowledge update <id> [--from <file.md> | --content <str>] [--name <n>] [--description <d>]
100434
- Update a doc \u2014 sends only the fields you pass
100435
- lotics knowledge rm <id> Archive a doc
100436
- lotics xlsx <subcommand> ... Read/write/edit .xlsx files on your local filesystem
100437
- (uses the bundled Lotics xlsx engine; prefer over
100438
- npm xlsx/exceljs for round-trip fidelity)
100439
- lotics docx <subcommand> ... Read/write/edit .docx files on your local filesystem
100440
- (uses the bundled Lotics OOXML engine; preserves
100441
- tables, images, and unknown markup verbatim)
100442
- lotics file upload <file|dir...> Upload files (alias: lotics upload)
100443
- lotics file download <file_id> Download a file by ID (alias: lotics download)
100444
- lotics file download record <record_id> <field_key>
100445
- Download all files on a record file field
100446
- lotics file preview <file|fil_id> [-o png]
100447
- Render a .docx/.xlsx to a PNG \u2014 a local path OR a
100448
- stored file id (downloaded first). Frontend engines;
100449
- needs a Chrome/Chromium on the machine
102080
+ ${renderCommandsHelp()}
100450
102081
 
100451
102082
  FLAGS
100452
102083
  --json Full JSON output (default is human-readable text)
@@ -100543,8 +102174,8 @@ function prompt(question) {
100543
102174
  });
100544
102175
  });
100545
102176
  }
100546
- async function publicPost(path10, body) {
100547
- const response = await fetch(`${API_BASE_URL}${path10}`, {
102177
+ async function publicPost(path12, body) {
102178
+ const response = await fetch(`${API_BASE_URL}${path12}`, {
100548
102179
  method: "POST",
100549
102180
  headers: { "Content-Type": "application/json" },
100550
102181
  body: JSON.stringify(body)
@@ -100669,7 +102300,7 @@ function announceTarget(ctx, workspaceId) {
100669
102300
  console.error(`lotics \u2192 ${org} / ${workspaceId}`);
100670
102301
  }
100671
102302
  function appManifestWorkspaceId(command, subcommand, pathArg, flags) {
100672
- if (command !== "app") return void 0;
102303
+ if (command !== "app" && command !== "upgrade") return void 0;
100673
102304
  if (flags.workspace || process.env.LOTICS_WORKSPACE) return void 0;
100674
102305
  if (subcommand === "create" || subcommand === "pull") return void 0;
100675
102306
  const dir = pathArg && !pathArg.startsWith("-") ? pathArg : process.cwd();
@@ -100712,13 +102343,13 @@ async function resolveWorkspace(client, ctx) {
100712
102343
  function resolveUploadPaths(rawPaths) {
100713
102344
  const result = [];
100714
102345
  for (const p of rawPaths) {
100715
- const resolved = path9.resolve(p);
100716
- const stat = fs11.statSync(resolved);
102346
+ const resolved = path11.resolve(p);
102347
+ const stat = fs13.statSync(resolved);
100717
102348
  if (stat.isDirectory()) {
100718
- const entries2 = fs11.readdirSync(resolved, { withFileTypes: true });
102349
+ const entries2 = fs13.readdirSync(resolved, { withFileTypes: true });
100719
102350
  for (const entry of entries2) {
100720
102351
  if (entry.isFile()) {
100721
- result.push(path9.join(resolved, entry.name));
102352
+ result.push(path11.join(resolved, entry.name));
100722
102353
  }
100723
102354
  }
100724
102355
  } else {
@@ -100742,14 +102373,90 @@ async function main() {
100742
102373
  subcommand = toolArgs;
100743
102374
  toolArgs = restArgs.shift();
100744
102375
  }
102376
+ if (flags.help && command === "report") {
102377
+ console.error(REPORT_USAGE);
102378
+ return;
102379
+ }
100745
102380
  if (flags.help || !command && !flags.version) {
100746
102381
  printHelp();
100747
102382
  return;
100748
102383
  }
102384
+ if (flags.version && command !== void 0) {
102385
+ console.error(
102386
+ `--version prints this CLI's version and takes no argument; it cannot be combined with a command.
102387
+ The CLI version: lotics --version
102388
+ A package version to take: operator-only for now \u2014 \`lotics upgrade\` takes the next release.`
102389
+ );
102390
+ process.exit(1);
102391
+ }
100749
102392
  if (flags.version) {
100750
102393
  console.log(VERSION);
100751
102394
  return;
100752
102395
  }
102396
+ if (command === "install") {
102397
+ if (!subcommand) {
102398
+ console.error("Usage: lotics install <package_id>");
102399
+ console.error(" Materializes a published package into the current workspace.");
102400
+ process.exit(1);
102401
+ }
102402
+ const { client: client2, ctx: ctx2 } = requireClient(flags);
102403
+ await resolveWorkspace(client2, ctx2);
102404
+ await installCommand(client2, { packageId: subcommand });
102405
+ return;
102406
+ }
102407
+ if (command === "upgrade") {
102408
+ const { client: client2, ctx: ctx2 } = requireClient(flags);
102409
+ applyAppManifestWorkspace(ctx2, command, subcommand, void 0, flags);
102410
+ await resolveWorkspace(client2, ctx2);
102411
+ await upgradeCommand(client2, {});
102412
+ return;
102413
+ }
102414
+ if (command === "docs") {
102415
+ docsCommand({ area: subcommand });
102416
+ return;
102417
+ }
102418
+ if (command === "report") {
102419
+ let input = subcommand ?? "";
102420
+ if (input.startsWith("@")) {
102421
+ const file2 = input.slice(1);
102422
+ if (!fs13.existsSync(file2)) {
102423
+ console.error(`No such file: ${file2}`);
102424
+ process.exit(1);
102425
+ }
102426
+ input = fs13.readFileSync(file2, "utf-8");
102427
+ } else if (input === "-") {
102428
+ input = await readStdin();
102429
+ }
102430
+ if (input.trim() === "") {
102431
+ console.error(REPORT_USAGE);
102432
+ process.exit(1);
102433
+ }
102434
+ const parsed2 = parseReport(input);
102435
+ if ("error" in parsed2) {
102436
+ console.error(`${parsed2.error}
102437
+ `);
102438
+ console.error(REPORT_USAGE);
102439
+ process.exit(1);
102440
+ }
102441
+ const reportCtx = resolveContext(flags);
102442
+ if (!reportCtx) {
102443
+ console.error(
102444
+ 'Not authenticated, so this could not be sent. Run "lotics auth signup", "lotics auth api-key <key>", or set LOTICS_API_KEY.'
102445
+ );
102446
+ echoReport(parsed2.report);
102447
+ process.exit(1);
102448
+ }
102449
+ noteScope({ org_id: reportCtx.orgId ?? void 0, workspace_id: reportCtx.workspaceId });
102450
+ await reportCommand(
102451
+ new LoticsClient({ apiKey: reportCtx.apiKey, workspaceId: reportCtx.workspaceId }),
102452
+ {
102453
+ report: parsed2.report,
102454
+ version: VERSION,
102455
+ workspaceId: reportCtx.workspaceId
102456
+ }
102457
+ );
102458
+ return;
102459
+ }
100753
102460
  if (command === "auth") {
100754
102461
  if (subcommand === "signup") {
100755
102462
  await handleSignup(toolArgs, flags);
@@ -100843,13 +102550,13 @@ async function main() {
100843
102550
  if (command === "preview") {
100844
102551
  if (subcommand && isStoredFileId(subcommand)) {
100845
102552
  const { client: client2 } = requireClient(flags);
100846
- const tmpDir = fs11.mkdtempSync(path9.join(os4.tmpdir(), "lotics-preview-"));
102553
+ const tmpDir = fs13.mkdtempSync(path11.join(os4.tmpdir(), "lotics-preview-"));
100847
102554
  try {
100848
102555
  const { path: localPath, filename } = await client2.downloadFileById(subcommand, tmpDir);
100849
102556
  const output = flags.output ?? defaultPreviewOutputPath(filename, process.cwd());
100850
102557
  await runPreviewCommand(localPath, { output });
100851
102558
  } finally {
100852
- fs11.rmSync(tmpDir, { recursive: true, force: true });
102559
+ fs13.rmSync(tmpDir, { recursive: true, force: true });
100853
102560
  }
100854
102561
  return;
100855
102562
  }
@@ -100932,9 +102639,12 @@ async function main() {
100932
102639
  }
100933
102640
  return;
100934
102641
  }
100935
- if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "knowledge") {
102642
+ if (!knownVerbs().has(command ?? "")) {
100936
102643
  console.error(`Unknown command: ${command}`);
100937
- console.error('Run "lotics --help" for usage.');
102644
+ console.error('Run "lotics --help" for usage \u2014 read \xA7 COMMANDS whole, not a grep.');
102645
+ console.error(
102646
+ `If Lotics genuinely cannot do this yet, say so \u2014 run \`lotics report\` for the frame.`
102647
+ );
100938
102648
  process.exit(1);
100939
102649
  }
100940
102650
  if (command === "app" && !subcommand) {
@@ -101188,7 +102898,7 @@ Available workspaces:`);
101188
102898
  const ingested = await ingestJsonArgs({
101189
102899
  rawArg: restArgs[1],
101190
102900
  stdinIsTTY: process.stdin.isTTY ?? false,
101191
- readFile: (p) => fs11.readFileSync(p, "utf-8"),
102901
+ readFile: (p) => fs13.readFileSync(p, "utf-8"),
101192
102902
  readStdin
101193
102903
  });
101194
102904
  if (ingested.kind === "error") {
@@ -101239,7 +102949,7 @@ Available workspaces:`);
101239
102949
  const ingested = await ingestJsonArgs({
101240
102950
  rawArg: restArgs[2],
101241
102951
  stdinIsTTY: process.stdin.isTTY ?? false,
101242
- readFile: (p) => fs11.readFileSync(p, "utf-8"),
102952
+ readFile: (p) => fs13.readFileSync(p, "utf-8"),
101243
102953
  readStdin
101244
102954
  });
101245
102955
  if (ingested.kind === "error") {
@@ -101395,7 +103105,7 @@ ${JSON.stringify(info.input_schema, null, 2)}`);
101395
103105
  const ingested = await ingestJsonArgs({
101396
103106
  rawArg: toolArgs,
101397
103107
  stdinIsTTY: process.stdin.isTTY ?? false,
101398
- readFile: (p) => fs11.readFileSync(p, "utf-8"),
103108
+ readFile: (p) => fs13.readFileSync(p, "utf-8"),
101399
103109
  readStdin
101400
103110
  });
101401
103111
  if (ingested.kind === "error") {