@lotics/cli 0.66.0 → 0.68.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.
@@ -911,7 +911,16 @@ args) {
911
911
  });
912
912
  console.error(`Deployed v${result.version_number} (${result.version_id})`);
913
913
  console.error(`Bundle size: ${(result.bundle_size_bytes / 1024).toFixed(1)} KB`);
914
- await warnIfUnbranded(client, meta.app_id);
914
+ // One post-deploy getApp feeds both advisory nudges (branding, unbound
915
+ // aliases). Deploy already succeeded — a failed check must not mask that.
916
+ try {
917
+ const app = await client.getApp(meta.app_id);
918
+ warnIfUnbranded(app);
919
+ warnIfUnboundAliases(app, meta.workflows ?? {}, meta.agents ?? {});
920
+ }
921
+ catch (err) {
922
+ console.error(`(skipped post-deploy checks: ${err.message})`);
923
+ }
915
924
  }
916
925
  catch (err) {
917
926
  const e = err;
@@ -935,24 +944,44 @@ args) {
935
944
  * generic tile in the launcher. Branding is set via `update_app` (the single
936
945
  * setter) — this only reminds; it never fails the deploy.
937
946
  */
938
- async function warnIfUnbranded(client, appId) {
939
- try {
940
- const app = await client.getApp(appId);
941
- const missing = [];
942
- if (!app.icon)
943
- missing.push("icon");
944
- if (!app.theme?.color)
945
- missing.push("color");
946
- if (missing.length === 0)
947
- return;
948
- console.error(`\n⚠ This app has no ${missing.join(" or ")} set — it shows a generic tile in the launcher.\n` +
949
- ` Set it: lotics run update_app '{"app_id":"${appId}","icon":"<lucide-name>","theme":{"color":"blue"}}'\n` +
950
- ` Find an icon: lotics run search_app_icons '{"query":"<word>"}'`);
951
- }
952
- catch (err) {
953
- // Deploy already succeeded; a failed branding check must not mask that.
954
- console.error(`(skipped branding check: ${err.message})`);
955
- }
947
+ function warnIfUnbranded(app) {
948
+ const missing = [];
949
+ if (!app.icon)
950
+ missing.push("icon");
951
+ if (!app.theme?.color)
952
+ missing.push("color");
953
+ if (missing.length === 0)
954
+ return;
955
+ console.error(`\n⚠ This app has no ${missing.join(" or ")} set — it shows a generic tile in the launcher.\n` +
956
+ ` Set it: lotics run update_app '{"app_id":"${app.id}","icon":"<lucide-name>","theme":{"color":"blue"}}'\n` +
957
+ ` Find an icon: lotics run search_app_icons '{"query":"<word>"}'`);
958
+ }
959
+ /**
960
+ * Non-blocking nudge after a successful deploy: catch manifest workflow/agent aliases
961
+ * that are NOT bound on the server. Deploy ships code + queries only — it never binds
962
+ * workflows/agents (`set_app_workflow` / `set_app_agent` own `apps.workflows`/`apps.agents`;
963
+ * the manifest maps are a pulled reflection). So an alias hand-added to `lotics.workflows`
964
+ * / `lotics.agents` (or bound then removed) compiles + deploys clean and only throws when
965
+ * the app first calls `useWorkflow` / `useAgentRun`. Surface that divergence HERE — at
966
+ * deploy time — instead of at the user's first click. Advisory only; never fails the deploy.
967
+ */
968
+ function warnIfUnboundAliases(app, declaredWorkflows, declaredAgents) {
969
+ const boundWorkflows = new Set(Object.keys(app.workflows ?? {}));
970
+ const boundAgents = new Set(Object.keys(app.agents ?? {}));
971
+ const unboundWorkflows = Object.keys(declaredWorkflows).filter((a) => !boundWorkflows.has(a));
972
+ const unboundAgents = Object.keys(declaredAgents).filter((a) => !boundAgents.has(a));
973
+ if (unboundWorkflows.length === 0 && unboundAgents.length === 0)
974
+ return;
975
+ const lines = [
976
+ "\n⚠ The manifest declares aliases that are NOT bound on the server. Deploy ships code +",
977
+ " queries only — it does NOT bind workflows/agents, so the app will throw \"has no … alias\"",
978
+ " the first time it calls them. Bind each one:",
979
+ ];
980
+ for (const alias of unboundWorkflows)
981
+ lines.push(` • workflow "${alias}" → lotics app workflow set ${alias}`);
982
+ for (const alias of unboundAgents)
983
+ lines.push(` • agent "${alias}" → bind with set_app_agent (lotics run set_app_agent …)`);
984
+ console.error(lines.join("\n"));
956
985
  }
957
986
  /**
958
987
  * `lotics app dev [path] [--port=5174] [--vite-port=5173]`
package/dist/src/cli.js CHANGED
@@ -30395,18 +30395,16 @@ function buildStarterTemplate(args) {
30395
30395
  },
30396
30396
  dependencies: {
30397
30397
  "@lotics/app-sdk": sdkVersion,
30398
- // Document preview engines for @lotics/ui FilePreview/FileGalleryModal
30399
- // (Word, Excel/CSV). Lazy-loaded an app that never previews them
30400
- // pays no bundle cost; only node_modules size grows.
30401
- "@lotics/docx": "^0.1.0",
30398
+ // The FilePreview engines (pdfjs-dist / @lotics/docx / @lotics/xlsx) are
30399
+ // NOT listed here: they ship as @lotics/ui dependencies (7.14.0+), so
30400
+ // every app gets them transitively one version source, no skew. An app
30401
+ // that IMPORTS @lotics/docx or @lotics/xlsx directly (its own doc/excel
30402
+ // builders) declares its own dependency then.
30402
30403
  "@lotics/ui": uiVersion,
30403
- "@lotics/xlsx": "^0.1.0",
30404
30404
  "@react-native-picker/picker": "^2.7.0",
30405
30405
  "expo-image": "~3.0.9",
30406
30406
  "lucide-react": "^0.562.0",
30407
30407
  "lucide-react-native": "^0.562.0",
30408
- // PDF preview engine for FilePreview/FileGalleryModal — also lazy-loaded.
30409
- "pdfjs-dist": "^6.0.0",
30410
30408
  react: "^19.0.0",
30411
30409
  "react-dom": "^19.0.0",
30412
30410
  "react-native": STARTER_REACT_NATIVE_VERSION,
@@ -32758,7 +32756,13 @@ async function appDeploy(client, args) {
32758
32756
  });
32759
32757
  console.error(`Deployed v${result.version_number} (${result.version_id})`);
32760
32758
  console.error(`Bundle size: ${(result.bundle_size_bytes / 1024).toFixed(1)} KB`);
32761
- await warnIfUnbranded(client, meta.app_id);
32759
+ try {
32760
+ const app = await client.getApp(meta.app_id);
32761
+ warnIfUnbranded(app);
32762
+ warnIfUnboundAliases(app, meta.workflows ?? {}, meta.agents ?? {});
32763
+ } catch (err2) {
32764
+ console.error(`(skipped post-deploy checks: ${err2.message})`);
32765
+ }
32762
32766
  } catch (err2) {
32763
32767
  const e = err2;
32764
32768
  if (e.code === "VERSION_CONFLICT") {
@@ -32774,22 +32778,32 @@ async function appDeploy(client, args) {
32774
32778
  if (fs4.existsSync(tmpDist)) fs4.unlinkSync(tmpDist);
32775
32779
  }
32776
32780
  }
32777
- async function warnIfUnbranded(client, appId) {
32778
- try {
32779
- const app = await client.getApp(appId);
32780
- const missing = [];
32781
- if (!app.icon) missing.push("icon");
32782
- if (!app.theme?.color) missing.push("color");
32783
- if (missing.length === 0) return;
32784
- console.error(
32785
- `
32781
+ function warnIfUnbranded(app) {
32782
+ const missing = [];
32783
+ if (!app.icon) missing.push("icon");
32784
+ if (!app.theme?.color) missing.push("color");
32785
+ if (missing.length === 0) return;
32786
+ console.error(
32787
+ `
32786
32788
  \u26A0 This app has no ${missing.join(" or ")} set \u2014 it shows a generic tile in the launcher.
32787
- Set it: lotics run update_app '{"app_id":"${appId}","icon":"<lucide-name>","theme":{"color":"blue"}}'
32789
+ Set it: lotics run update_app '{"app_id":"${app.id}","icon":"<lucide-name>","theme":{"color":"blue"}}'
32788
32790
  Find an icon: lotics run search_app_icons '{"query":"<word>"}'`
32789
- );
32790
- } catch (err2) {
32791
- console.error(`(skipped branding check: ${err2.message})`);
32792
- }
32791
+ );
32792
+ }
32793
+ function warnIfUnboundAliases(app, declaredWorkflows, declaredAgents) {
32794
+ const boundWorkflows = new Set(Object.keys(app.workflows ?? {}));
32795
+ const boundAgents = new Set(Object.keys(app.agents ?? {}));
32796
+ const unboundWorkflows = Object.keys(declaredWorkflows).filter((a) => !boundWorkflows.has(a));
32797
+ const unboundAgents = Object.keys(declaredAgents).filter((a) => !boundAgents.has(a));
32798
+ if (unboundWorkflows.length === 0 && unboundAgents.length === 0) return;
32799
+ const lines = [
32800
+ "\n\u26A0 The manifest declares aliases that are NOT bound on the server. Deploy ships code +",
32801
+ ' queries only \u2014 it does NOT bind workflows/agents, so the app will throw "has no \u2026 alias"',
32802
+ " the first time it calls them. Bind each one:"
32803
+ ];
32804
+ for (const alias of unboundWorkflows) lines.push(` \u2022 workflow "${alias}" \u2192 lotics app workflow set ${alias}`);
32805
+ for (const alias of unboundAgents) lines.push(` \u2022 agent "${alias}" \u2192 bind with set_app_agent (lotics run set_app_agent \u2026)`);
32806
+ console.error(lines.join("\n"));
32793
32807
  }
32794
32808
  async function appDev(client, args) {
32795
32809
  const projectDir = path5.resolve(args.projectDir ?? process.cwd());
@@ -42716,6 +42730,7 @@ function readCfbStreams(bytes) {
42716
42730
  throw new Error("Unsupported .xls: invalid OLE2 sector size");
42717
42731
  }
42718
42732
  const sectorOffset = (n) => sectorSize * (n + 1);
42733
+ const maxSectors = Math.ceil(bytes.length / sectorSize) + 1;
42719
42734
  const difat = [];
42720
42735
  for (let i2 = 0; i2 < 109; i2++) {
42721
42736
  const v = u32(76 + i2 * 4);
@@ -42723,7 +42738,9 @@ function readCfbStreams(bytes) {
42723
42738
  }
42724
42739
  let ds = firstDifatSector;
42725
42740
  const entriesPerSector = sectorSize / 4;
42726
- for (let guard = 0; ds !== ENDOFCHAIN && ds !== FREESECT && guard < 1 << 20; guard++) {
42741
+ const seenDifat = /* @__PURE__ */ new Set();
42742
+ while (ds !== ENDOFCHAIN && ds !== FREESECT && ds < maxSectors && !seenDifat.has(ds)) {
42743
+ seenDifat.add(ds);
42727
42744
  const base = sectorOffset(ds);
42728
42745
  for (let i2 = 0; i2 < entriesPerSector - 1; i2++) {
42729
42746
  const v = u32(base + i2 * 4);
@@ -42733,14 +42750,18 @@ function readCfbStreams(bytes) {
42733
42750
  }
42734
42751
  const fat = [];
42735
42752
  for (const fatSector of difat) {
42753
+ if (fat.length >= maxSectors) break;
42754
+ if (fatSector >= maxSectors) continue;
42736
42755
  const base = sectorOffset(fatSector);
42737
- for (let i2 = 0; i2 < entriesPerSector; i2++) fat.push(u32(base + i2 * 4));
42756
+ for (let i2 = 0; i2 < entriesPerSector && fat.length < maxSectors; i2++) {
42757
+ fat.push(u32(base + i2 * 4));
42758
+ }
42738
42759
  }
42739
42760
  const readChain = (src, start, size, ss, offOf, chain) => {
42740
- const maxSectors = Math.floor(src.length / ss) + 2;
42761
+ const maxSectors2 = Math.floor(src.length / ss) + 2;
42741
42762
  const parts = [];
42742
42763
  let s = start;
42743
- for (let guard = 0; s !== ENDOFCHAIN && s !== FREESECT && s < chain.length && guard < maxSectors; guard++) {
42764
+ for (let guard = 0; s !== ENDOFCHAIN && s !== FREESECT && s < chain.length && guard < maxSectors2; guard++) {
42744
42765
  const o = offOf(s);
42745
42766
  parts.push(src.subarray(o, o + ss));
42746
42767
  s = chain[s];
@@ -80,18 +80,16 @@ export function buildStarterTemplate(args) {
80
80
  },
81
81
  dependencies: {
82
82
  "@lotics/app-sdk": sdkVersion,
83
- // Document preview engines for @lotics/ui FilePreview/FileGalleryModal
84
- // (Word, Excel/CSV). Lazy-loaded an app that never previews them
85
- // pays no bundle cost; only node_modules size grows.
86
- "@lotics/docx": "^0.1.0",
83
+ // The FilePreview engines (pdfjs-dist / @lotics/docx / @lotics/xlsx) are
84
+ // NOT listed here: they ship as @lotics/ui dependencies (7.14.0+), so
85
+ // every app gets them transitively one version source, no skew. An app
86
+ // that IMPORTS @lotics/docx or @lotics/xlsx directly (its own doc/excel
87
+ // builders) declares its own dependency then.
87
88
  "@lotics/ui": uiVersion,
88
- "@lotics/xlsx": "^0.1.0",
89
89
  "@react-native-picker/picker": "^2.7.0",
90
90
  "expo-image": "~3.0.9",
91
91
  "lucide-react": "^0.562.0",
92
92
  "lucide-react-native": "^0.562.0",
93
- // PDF preview engine for FilePreview/FileGalleryModal — also lazy-loaded.
94
- "pdfjs-dist": "^6.0.0",
95
93
  react: "^19.0.0",
96
94
  "react-dom": "^19.0.0",
97
95
  "react-native": STARTER_REACT_NATIVE_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.66.0",
3
+ "version": "0.68.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,7 +26,7 @@
26
26
  "@types/node": "^22.15.21",
27
27
  "esbuild": "^0.28.1",
28
28
  "typescript": "^6.0.3",
29
- "vitest": "^4.1.7"
29
+ "vitest": "^4.1.9"
30
30
  },
31
31
  "keywords": [
32
32
  "lotics",