@lotics/cli 0.65.0 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -181,8 +181,14 @@ lotics app workflow pull # rewrite src/workflows/*.ts + globa
181
181
  lotics app workflow check # typecheck every body locally ([alias] for one)
182
182
  lotics app workflow set issueInvoice # push the edited src/workflows/issueInvoice.ts
183
183
 
184
- # Dev-link @lotics/ui to the monorepo's packages/ui/src for live HMR (monorepo only)
185
- lotics ui link card # edits to packages/ui/src go live
184
+ # Iterate on a named query WITHOUT a deploy: push package.json#lotics.queries.<alias>
185
+ # to apps.queries (server-validated like a deploy). apps.queries is manifest-
186
+ # authoritative, so the next `app deploy` re-syncs it — keep the manifest current.
187
+ lotics app query set openInvoices # push package.json#lotics.queries.openInvoices
188
+
189
+ # Dev-link @lotics/ui to packages/ui/src for live HMR (Vite alias; deploy bundles it)
190
+ lotics ui link card # monorepo: packages/ui/src found automatically
191
+ lotics ui link card --ui-src /abs/monorepo/packages/ui/src # external app (e.g. ~/lotics_apps)
186
192
  lotics ui link card --remove # finalize: PR + publish, then drop the alias
187
193
  ```
188
194
 
@@ -303,6 +303,23 @@ export declare function appExecuteWorkflow(client: LoticsClient, args: {
303
303
  export declare function appWorkflowSet(client: LoticsClient, args: {
304
304
  alias: string;
305
305
  }): Promise<void>;
306
+ /**
307
+ * `lotics app query set <alias>` — push `package.json#lotics.queries.<alias>` to
308
+ * `apps.queries` through `set_app_query`, WITHOUT a deploy. The deploy-free inner
309
+ * loop for named queries, parallel to `lotics app workflow set` for workflows.
310
+ *
311
+ * The declaration (`{ ast, params? }`) is read from the manifest — the same map
312
+ * `useQuery` codegen reads and `lotics app deploy` syncs authoritatively. The
313
+ * server validates it exactly as a deploy does (alias identifier, workspace-only
314
+ * tables, resolvable fields, declared params). Because `apps.queries` is
315
+ * manifest-authoritative, the next `lotics app deploy` overwrites this from the
316
+ * manifest — so keep the manifest as the source of truth; this only skips the
317
+ * build/upload round-trip while iterating. Errors (unbound alias, validation
318
+ * failure) print to stderr and exit non-zero.
319
+ */
320
+ export declare function appQuerySet(client: LoticsClient, args: {
321
+ alias: string;
322
+ }): Promise<void>;
306
323
  /**
307
324
  * `lotics app workflow pull` — rewrite every `src/workflows/<alias>.ts` from the
308
325
  * server without a full `lotics app pull` (no source archive, no npm install).
@@ -346,4 +363,5 @@ export declare function appUiLink(args: {
346
363
  projectDir?: string;
347
364
  component: string;
348
365
  remove?: boolean;
366
+ uiSrc?: string;
349
367
  }): void;
@@ -896,9 +896,13 @@ args) {
896
896
  // `capabilities` block turns every capability OFF on the next deploy
897
897
  // (fail-safe; the declaration is the grant).
898
898
  capabilities: meta.capabilities ?? {},
899
- // Workflow bindings are NOT a deploy concern — set_app_workflow /
900
- // remove_app_workflow own apps.workflows. The manifest's `workflows`
901
- // map is a pulled reflection used only for the .d.ts codegen above.
899
+ // Workflow BINDINGS are NOT a deploy concern — set_app_workflow /
900
+ // remove_app_workflow own apps.workflows. But the alias KEYS of the
901
+ // manifest's `workflows` map ARE sent (never the bindings): they record
902
+ // which aliases this bundle declares, so remove_app_workflow can refuse
903
+ // to unbind an alias the served version still calls. Drop an alias from
904
+ // the manifest + redeploy to lift that guard before removing its binding.
905
+ workflow_aliases: Object.keys(meta.workflows ?? {}),
902
906
  });
903
907
  writeAppMeta(projectDir, {
904
908
  ...meta,
@@ -1198,6 +1202,37 @@ export async function appWorkflowSet(client, args) {
1198
1202
  console.error(` result.data schema: ${JSON.stringify(result.outputs)}`);
1199
1203
  }
1200
1204
  }
1205
+ /**
1206
+ * `lotics app query set <alias>` — push `package.json#lotics.queries.<alias>` to
1207
+ * `apps.queries` through `set_app_query`, WITHOUT a deploy. The deploy-free inner
1208
+ * loop for named queries, parallel to `lotics app workflow set` for workflows.
1209
+ *
1210
+ * The declaration (`{ ast, params? }`) is read from the manifest — the same map
1211
+ * `useQuery` codegen reads and `lotics app deploy` syncs authoritatively. The
1212
+ * server validates it exactly as a deploy does (alias identifier, workspace-only
1213
+ * tables, resolvable fields, declared params). Because `apps.queries` is
1214
+ * manifest-authoritative, the next `lotics app deploy` overwrites this from the
1215
+ * manifest — so keep the manifest as the source of truth; this only skips the
1216
+ * build/upload round-trip while iterating. Errors (unbound alias, validation
1217
+ * failure) print to stderr and exit non-zero.
1218
+ */
1219
+ export async function appQuerySet(client, args) {
1220
+ const projectDir = process.cwd();
1221
+ const meta = readAppMeta(projectDir);
1222
+ const declaration = meta.queries?.[args.alias];
1223
+ if (!declaration) {
1224
+ console.error(`No query "${args.alias}" in package.json#lotics.queries. ` +
1225
+ `Declare it there (alias → { ast, params? }) first.`);
1226
+ process.exit(1);
1227
+ }
1228
+ const res = await client.setAppQuery(meta.app_id, args.alias, declaration);
1229
+ if (res.error) {
1230
+ console.error(`Failed to set query "${args.alias}": ${res.error}`);
1231
+ process.exit(1);
1232
+ }
1233
+ console.error(`Set query "${args.alias}" on ${meta.app_id}. ` +
1234
+ `(apps.queries is manifest-authoritative — the next 'lotics app deploy' re-syncs it.)`);
1235
+ }
1201
1236
  /**
1202
1237
  * `lotics app workflow pull` — rewrite every `src/workflows/<alias>.ts` from the
1203
1238
  * server without a full `lotics app pull` (no source archive, no npm install).
@@ -1358,11 +1393,18 @@ export function appUiLink(args) {
1358
1393
  if (!fs.existsSync(viteConfigPath)) {
1359
1394
  throw new Error(`No vite.config.ts at ${projectDir}. Run inside a 'lotics app' project directory.`);
1360
1395
  }
1361
- const uiSrc = findUiSrcDir(projectDir);
1362
- if (!uiSrc) {
1363
- throw new Error("Cannot find packages/ui/src by walking up from this directory — `lotics ui link` " +
1364
- "requires a monorepo checkout. External apps consume @lotics/ui from npm; bump the " +
1365
- "package version and widen the app's dependency range instead.");
1396
+ // Resolve packages/ui/src. An explicit --ui-src / LOTICS_UI_SRC wins — that's how
1397
+ // an EXTERNAL app (one that consumes @lotics/ui from npm, with no monorepo above
1398
+ // it) links the local kit; otherwise walk up for a monorepo checkout.
1399
+ const explicit = args.uiSrc ?? process.env.LOTICS_UI_SRC;
1400
+ const uiSrc = explicit ? path.resolve(explicit) : findUiSrcDir(projectDir);
1401
+ if (!uiSrc || !fs.existsSync(uiSrc) || !fs.statSync(uiSrc).isDirectory()) {
1402
+ throw new Error(explicit
1403
+ ? `--ui-src / LOTICS_UI_SRC points at '${explicit}', which is not a directory. ` +
1404
+ `Pass the absolute path to the monorepo's packages/ui/src.`
1405
+ : "Cannot find packages/ui/src by walking up from this directory. For an EXTERNAL app " +
1406
+ "(consuming @lotics/ui from npm), pass --ui-src=<abs path to packages/ui/src> or set " +
1407
+ "LOTICS_UI_SRC; inside a monorepo checkout it is found automatically.");
1366
1408
  }
1367
1409
  // Validate the named component exists in src so a typo fails loud (the alias
1368
1410
  // itself stays package-wide — this is the advisory check the spec calls for).
@@ -1403,6 +1445,10 @@ export function appUiLink(args) {
1403
1445
  fs.writeFileSync(viteConfigPath, updated);
1404
1446
  console.error(`Dev-linked @lotics/ui → ${uiSrc} in ${viteConfigPath}.`);
1405
1447
  console.error("Restart `lotics app dev` and rm -rf node_modules/.vite to clear cached modules.");
1448
+ // The app's tsc still resolves @lotics/ui from node_modules (the published .d.ts) —
1449
+ // the kit `src` can't be typechecked in an app because it's RN-Web (uses
1450
+ // react-native-web types the app resolves as base react-native). Typecheck the kit
1451
+ // in packages/ui; the finalize publish restores the app's own typecheck.
1406
1452
  console.error("Finalize: PR the packages/ui change → publish → `lotics ui link <component> --remove` + bump the app's dep.");
1407
1453
  }
1408
1454
  /** Escape a string for literal use inside a RegExp. */
@@ -208,8 +208,9 @@ describe("appCodegen (.d.ts-only path)", () => {
208
208
  });
209
209
  /**
210
210
  * `appUiLink` edits the app's vite.config.ts resolve.alias to dev-link
211
- * @lotics/ui at the monorepo's packages/ui/src. It fails loud outside a
212
- * monorepo, validates the component exists, and insert/remove is idempotent.
211
+ * @lotics/ui at packages/ui/src auto-found in a monorepo checkout, or given
212
+ * explicitly via --ui-src for an external app. It validates the src + component,
213
+ * fails loud when neither resolves, and insert/remove is idempotent.
213
214
  */
214
215
  describe("appUiLink", () => {
215
216
  let root;
@@ -264,16 +265,30 @@ describe("appUiLink", () => {
264
265
  it("fails loud when the named component does not exist in packages/ui/src", () => {
265
266
  expect(() => appUiLink({ projectDir: appDir, component: "nonexistent" })).toThrow(/nonexistent/);
266
267
  });
267
- it("fails loud when there is no monorepo packages/ui/src above the project", () => {
268
+ it("fails loud when there is no packages/ui/src and no --ui-src override", () => {
268
269
  const lonely = fs.mkdtempSync(path.join(tmpdir(), "lotics-lonely-app-"));
269
270
  fs.writeFileSync(path.join(lonely, "vite.config.ts"), "export default { resolve: { alias: [] } };");
270
271
  try {
271
- expect(() => appUiLink({ projectDir: lonely, component: "card" })).toThrow(/monorepo checkout/);
272
+ expect(() => appUiLink({ projectDir: lonely, component: "card" })).toThrow(/--ui-src/);
272
273
  }
273
274
  finally {
274
275
  fs.rmSync(lonely, { recursive: true, force: true });
275
276
  }
276
277
  });
278
+ it("links an EXTERNAL app (no monorepo above) via an explicit --ui-src", () => {
279
+ const ext = fs.mkdtempSync(path.join(tmpdir(), "lotics-external-app-"));
280
+ fs.writeFileSync(path.join(ext, "vite.config.ts"), "export default { resolve: { alias: [] } };");
281
+ try {
282
+ appUiLink({ projectDir: ext, component: "card", uiSrc });
283
+ expect(fs.readFileSync(path.join(ext, "vite.config.ts"), "utf-8")).toContain(uiSrc);
284
+ }
285
+ finally {
286
+ fs.rmSync(ext, { recursive: true, force: true });
287
+ }
288
+ });
289
+ it("fails loud when --ui-src is not a directory", () => {
290
+ expect(() => appUiLink({ projectDir: appDir, component: "card", uiSrc: path.join(root, "nope") })).toThrow(/not a directory/);
291
+ });
277
292
  });
278
293
  /**
279
294
  * The workflow body files are how AA-1 Option B becomes "open a file → edit →
package/dist/args.d.ts CHANGED
@@ -23,6 +23,8 @@ export declare function parseArgs(argv: string[]): {
23
23
  apiKey?: string;
24
24
  workspace?: string;
25
25
  viewAs?: string;
26
+ /** `--ui-src <path>`: absolute packages/ui/src for `ui link` in an external app. */
27
+ uiSrc?: string;
26
28
  name?: string;
27
29
  timezone?: string;
28
30
  message?: string;
package/dist/args.js CHANGED
@@ -19,6 +19,7 @@ export function parseArgs(argv) {
19
19
  apiKey: undefined,
20
20
  workspace: undefined,
21
21
  viewAs: undefined,
22
+ uiSrc: undefined,
22
23
  name: undefined,
23
24
  timezone: undefined,
24
25
  message: undefined,
@@ -60,6 +61,9 @@ export function parseArgs(argv) {
60
61
  case "--view-as":
61
62
  flags.viewAs = argv[++i];
62
63
  break;
64
+ case "--ui-src":
65
+ flags.uiSrc = argv[++i];
66
+ break;
63
67
  case "--name":
64
68
  flags.name = argv[++i];
65
69
  break;
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ net.setDefaultAutoSelectFamilyAttemptTimeout(2000);
13
13
  import { LoticsClient, API_BASE_URL } from "./client.js";
14
14
  import { resolveContext, deleteConfig, getConfigPath, loadGlobalConfig, saveGlobalConfig, loadLocalConfig, upsertProfile, removeProfile, setActiveOrg, setSelectedWorkspace, resolveProfileByNameOrId, checkForUpdate, } from "./config.js";
15
15
  import { VERSION } from "./version.js";
16
- import { appCreate, appPull, appDeploy, appDev, appSetSubdomain, appRename, appVersions, appCodegen, appExecuteWorkflow, appWorkflowSet, appWorkflowPull, appWorkflowCheck, appUiLink, } from "./app_commands.js";
16
+ import { appCreate, appPull, appDeploy, appDev, appSetSubdomain, appRename, appVersions, appCodegen, appExecuteWorkflow, appWorkflowSet, appWorkflowPull, appWorkflowCheck, appQuerySet, appUiLink, } from "./app_commands.js";
17
17
  import { parseArgs } from "./args.js";
18
18
  import { ingestJsonArgs } from "./inputs.js";
19
19
  import { runXlsxCommand } from "./xlsx.js";
@@ -90,11 +90,16 @@ COMMANDS
90
90
  lotics app workflow pull Rewrite src/workflows/*.ts from the server
91
91
  lotics app workflow check [alias] Typecheck src/workflows bodies locally (one
92
92
  isolated program per alias; the app's own tsc)
93
+ lotics app query set <alias> Push package.json#lotics.queries.<alias> to
94
+ apps.queries via set_app_query (no deploy;
95
+ re-synced by the next deploy from the manifest)
93
96
  lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
94
97
  lotics app rename "<new name>" Rename the app's display name (launcher title)
95
98
  lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
96
- lotics ui link <component> [--remove] Dev-link @lotics/ui to the monorepo's
97
- packages/ui/src for live HMR (monorepo only)
99
+ lotics ui link <component> [--ui-src <path>] [--remove]
100
+ Dev-link @lotics/ui to packages/ui/src (Vite alias
101
+ + tsc paths) for live HMR + typecheck. Monorepo apps
102
+ auto-find it; external apps pass --ui-src / LOTICS_UI_SRC
98
103
  lotics xlsx <subcommand> ... Read/write/edit .xlsx files on your local filesystem
99
104
  (uses the bundled Lotics xlsx engine; prefer over
100
105
  npm xlsx/exceljs for round-trip fidelity)
@@ -493,17 +498,19 @@ async function main() {
493
498
  if (subcommand === "link") {
494
499
  const component = toolArgs;
495
500
  if (!component) {
496
- console.error("Usage: lotics ui link <component> [--remove]");
497
- console.error("Dev-links @lotics/ui to the monorepo's packages/ui/src for live HMR.");
501
+ console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
502
+ console.error("Dev-links @lotics/ui to packages/ui/src (Vite + tsc) for live HMR + typecheck.");
503
+ console.error("Monorepo apps find packages/ui/src automatically; external apps pass --ui-src / LOTICS_UI_SRC.");
498
504
  process.exit(1);
499
505
  }
500
506
  // `--remove` isn't a value-taking flag, so the parser leaves it as a
501
- // trailing positional (the component took `toolArgs`).
502
- appUiLink({ component, remove: restArgs.includes("--remove") });
507
+ // trailing positional (the component took `toolArgs`); `--ui-src` IS a
508
+ // value flag (flags.uiSrc), falling back to LOTICS_UI_SRC inside appUiLink.
509
+ appUiLink({ component, uiSrc: flags.uiSrc, remove: restArgs.includes("--remove") });
503
510
  return;
504
511
  }
505
512
  console.error(`Unknown ui subcommand: ${subcommand ?? "(none)"}`);
506
- console.error("Usage: lotics ui link <component> [--remove]");
513
+ console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
507
514
  process.exit(1);
508
515
  }
509
516
  // --- lotics app workflow check [alias] — local typecheck, no auth, no network ---
@@ -606,6 +613,7 @@ async function main() {
606
613
  console.error(" lotics app workflow set <alias> Push the edited src/workflows/<alias>.ts body");
607
614
  console.error(" lotics app workflow pull Rewrite src/workflows/*.ts from the server");
608
615
  console.error(" lotics app workflow check [alias] Typecheck src/workflows bodies locally");
616
+ console.error(" lotics app query set <alias> Push lotics.queries.<alias> to apps.queries (no deploy)");
609
617
  console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
610
618
  console.error(" lotics app rename \"<new name>\" Rename the app's display name (launcher title)");
611
619
  console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
@@ -826,6 +834,25 @@ async function main() {
826
834
  }
827
835
  workflowUsage();
828
836
  }
837
+ if (subcommand === "query") {
838
+ // `lotics app query set <alias>` — push package.json#lotics.queries.<alias>
839
+ // to apps.queries via set_app_query, no deploy. `toolArgs` is the verb;
840
+ // `restArgs` carries the alias.
841
+ const action = toolArgs;
842
+ if (action === "set") {
843
+ const alias = restArgs[0];
844
+ if (!alias) {
845
+ console.error("Usage: lotics app query set <alias>");
846
+ console.error("Pushes package.json#lotics.queries.<alias> to apps.queries via set_app_query (no deploy).");
847
+ process.exit(1);
848
+ }
849
+ await appQuerySet(client, { alias });
850
+ return;
851
+ }
852
+ console.error("Usage:");
853
+ console.error(" lotics app query set <alias> Push package.json#lotics.queries.<alias> to apps.queries (no deploy)");
854
+ process.exit(1);
855
+ }
829
856
  if (subcommand === "dev") {
830
857
  // First positional is an optional project path (defaults to cwd).
831
858
  // --port and --vite-port can override the wrapper / Vite ports.
package/dist/client.d.ts CHANGED
@@ -277,6 +277,18 @@ export declare class LoticsClient {
277
277
  name?: string;
278
278
  description?: string;
279
279
  }): Promise<ToolExecuteResult>;
280
+ /**
281
+ * Bind (create or replace) an app query by alias via the `set_app_query` tool
282
+ * — the deploy-free authoring path for `apps.queries`, parallel to
283
+ * `setAppWorkflow`. `declaration` is the `{ ast, params? }` from
284
+ * `package.json#lotics.queries.<alias>`. The server validates it exactly as a
285
+ * deploy validates the manifest. Note: `apps.queries` is manifest-authoritative,
286
+ * so the next `lotics app deploy` overwrites this from the manifest.
287
+ */
288
+ setAppQuery(app_id: string, alias: string, declaration: {
289
+ ast: unknown;
290
+ params?: Record<string, unknown>;
291
+ }): Promise<ToolExecuteResult>;
280
292
  /**
281
293
  * Fetch one app workflow's faithful source + bound input/output schemas via
282
294
  * `get_app_workflow`. `source` is the JS-subset body re-rendered from the
@@ -375,6 +387,14 @@ export declare class LoticsClient {
375
387
  capabilities?: {
376
388
  comments?: boolean;
377
389
  };
390
+ /**
391
+ * The manifest's `lotics.workflows` KEYS — the workflow aliases the deployed
392
+ * code declares (NOT the bindings; `set_app_workflow` / `remove_app_workflow`
393
+ * own `apps.workflows`). Recorded on the new version so `remove_app_workflow`
394
+ * refuses to unbind an alias the served version still calls. Always sent
395
+ * (empty array when none declared).
396
+ */
397
+ workflow_aliases?: string[];
378
398
  }): Promise<{
379
399
  version_id: string;
380
400
  version_number: number;
package/dist/client.js CHANGED
@@ -333,6 +333,17 @@ export class LoticsClient {
333
333
  ...(body.description ? { description: body.description } : {}),
334
334
  });
335
335
  }
336
+ /**
337
+ * Bind (create or replace) an app query by alias via the `set_app_query` tool
338
+ * — the deploy-free authoring path for `apps.queries`, parallel to
339
+ * `setAppWorkflow`. `declaration` is the `{ ast, params? }` from
340
+ * `package.json#lotics.queries.<alias>`. The server validates it exactly as a
341
+ * deploy validates the manifest. Note: `apps.queries` is manifest-authoritative,
342
+ * so the next `lotics app deploy` overwrites this from the manifest.
343
+ */
344
+ async setAppQuery(app_id, alias, declaration) {
345
+ return this.execute("set_app_query", { app_id, alias, declaration });
346
+ }
336
347
  /**
337
348
  * Fetch one app workflow's faithful source + bound input/output schemas via
338
349
  * `get_app_workflow`. `source` is the JS-subset body re-rendered from the
@@ -427,6 +438,10 @@ export class LoticsClient {
427
438
  if (args.capabilities !== undefined) {
428
439
  formData.append("capabilities", JSON.stringify(args.capabilities));
429
440
  }
441
+ // Always send the declared workflow aliases (empty array when none) so the
442
+ // server records what the served version calls — the remove_app_workflow
443
+ // guard reads this back. These are the manifest KEYS only, never bindings.
444
+ formData.append("workflow_aliases", JSON.stringify(args.workflow_aliases ?? []));
430
445
  const url = `${this.baseUrl}/v1/apps/${encodeURIComponent(args.app_id)}/versions`;
431
446
  const response = await fetch(url, {
432
447
  method: "POST",
package/dist/src/cli.js CHANGED
@@ -29881,6 +29881,17 @@ var LoticsClient = class {
29881
29881
  ...body.description ? { description: body.description } : {}
29882
29882
  });
29883
29883
  }
29884
+ /**
29885
+ * Bind (create or replace) an app query by alias via the `set_app_query` tool
29886
+ * — the deploy-free authoring path for `apps.queries`, parallel to
29887
+ * `setAppWorkflow`. `declaration` is the `{ ast, params? }` from
29888
+ * `package.json#lotics.queries.<alias>`. The server validates it exactly as a
29889
+ * deploy validates the manifest. Note: `apps.queries` is manifest-authoritative,
29890
+ * so the next `lotics app deploy` overwrites this from the manifest.
29891
+ */
29892
+ async setAppQuery(app_id, alias, declaration) {
29893
+ return this.execute("set_app_query", { app_id, alias, declaration });
29894
+ }
29884
29895
  /**
29885
29896
  * Fetch one app workflow's faithful source + bound input/output schemas via
29886
29897
  * `get_app_workflow`. `source` is the JS-subset body re-rendered from the
@@ -30004,6 +30015,7 @@ var LoticsClient = class {
30004
30015
  if (args.capabilities !== void 0) {
30005
30016
  formData.append("capabilities", JSON.stringify(args.capabilities));
30006
30017
  }
30018
+ formData.append("workflow_aliases", JSON.stringify(args.workflow_aliases ?? []));
30007
30019
  const url = `${this.baseUrl}/v1/apps/${encodeURIComponent(args.app_id)}/versions`;
30008
30020
  const response = await fetch(url, {
30009
30021
  method: "POST",
@@ -32730,10 +32742,14 @@ async function appDeploy(client, args) {
32730
32742
  // defaulting to `{}` when the manifest declares none — so deleting the
32731
32743
  // `capabilities` block turns every capability OFF on the next deploy
32732
32744
  // (fail-safe; the declaration is the grant).
32733
- capabilities: meta.capabilities ?? {}
32734
- // Workflow bindings are NOT a deploy concern — set_app_workflow /
32735
- // remove_app_workflow own apps.workflows. The manifest's `workflows`
32736
- // map is a pulled reflection used only for the .d.ts codegen above.
32745
+ capabilities: meta.capabilities ?? {},
32746
+ // Workflow BINDINGS are NOT a deploy concern — set_app_workflow /
32747
+ // remove_app_workflow own apps.workflows. But the alias KEYS of the
32748
+ // manifest's `workflows` map ARE sent (never the bindings): they record
32749
+ // which aliases this bundle declares, so remove_app_workflow can refuse
32750
+ // to unbind an alias the served version still calls. Drop an alias from
32751
+ // the manifest + redeploy to lift that guard before removing its binding.
32752
+ workflow_aliases: Object.keys(meta.workflows ?? {})
32737
32753
  });
32738
32754
  writeAppMeta(projectDir, {
32739
32755
  ...meta,
@@ -32942,6 +32958,25 @@ async function appWorkflowSet(client, args) {
32942
32958
  console.error(` result.data schema: ${JSON.stringify(result.outputs)}`);
32943
32959
  }
32944
32960
  }
32961
+ async function appQuerySet(client, args) {
32962
+ const projectDir = process.cwd();
32963
+ const meta = readAppMeta(projectDir);
32964
+ const declaration = meta.queries?.[args.alias];
32965
+ if (!declaration) {
32966
+ console.error(
32967
+ `No query "${args.alias}" in package.json#lotics.queries. Declare it there (alias \u2192 { ast, params? }) first.`
32968
+ );
32969
+ process.exit(1);
32970
+ }
32971
+ const res = await client.setAppQuery(meta.app_id, args.alias, declaration);
32972
+ if (res.error) {
32973
+ console.error(`Failed to set query "${args.alias}": ${res.error}`);
32974
+ process.exit(1);
32975
+ }
32976
+ console.error(
32977
+ `Set query "${args.alias}" on ${meta.app_id}. (apps.queries is manifest-authoritative \u2014 the next 'lotics app deploy' re-syncs it.)`
32978
+ );
32979
+ }
32945
32980
  async function appWorkflowPull(client) {
32946
32981
  const projectDir = process.cwd();
32947
32982
  const meta = readAppMeta(projectDir);
@@ -33050,10 +33085,11 @@ function appUiLink(args) {
33050
33085
  `No vite.config.ts at ${projectDir}. Run inside a 'lotics app' project directory.`
33051
33086
  );
33052
33087
  }
33053
- const uiSrc = findUiSrcDir(projectDir);
33054
- if (!uiSrc) {
33088
+ const explicit = args.uiSrc ?? process.env.LOTICS_UI_SRC;
33089
+ const uiSrc = explicit ? path5.resolve(explicit) : findUiSrcDir(projectDir);
33090
+ if (!uiSrc || !fs4.existsSync(uiSrc) || !fs4.statSync(uiSrc).isDirectory()) {
33055
33091
  throw new Error(
33056
- "Cannot find packages/ui/src by walking up from this directory \u2014 `lotics ui link` requires a monorepo checkout. External apps consume @lotics/ui from npm; bump the package version and widen the app's dependency range instead."
33092
+ explicit ? `--ui-src / LOTICS_UI_SRC points at '${explicit}', which is not a directory. Pass the absolute path to the monorepo's packages/ui/src.` : "Cannot find packages/ui/src by walking up from this directory. For an EXTERNAL app (consuming @lotics/ui from npm), pass --ui-src=<abs path to packages/ui/src> or set LOTICS_UI_SRC; inside a monorepo checkout it is found automatically."
33057
33093
  );
33058
33094
  }
33059
33095
  const hasComponent = fs4.existsSync(path5.join(uiSrc, `${args.component}.tsx`)) || fs4.existsSync(path5.join(uiSrc, `${args.component}.ts`)) || fs4.existsSync(path5.join(uiSrc, args.component));
@@ -33108,6 +33144,7 @@ function parseArgs(argv) {
33108
33144
  apiKey: void 0,
33109
33145
  workspace: void 0,
33110
33146
  viewAs: void 0,
33147
+ uiSrc: void 0,
33111
33148
  name: void 0,
33112
33149
  timezone: void 0,
33113
33150
  message: void 0,
@@ -33149,6 +33186,9 @@ function parseArgs(argv) {
33149
33186
  case "--view-as":
33150
33187
  flags.viewAs = argv[++i2];
33151
33188
  break;
33189
+ case "--ui-src":
33190
+ flags.uiSrc = argv[++i2];
33191
+ break;
33152
33192
  case "--name":
33153
33193
  flags.name = argv[++i2];
33154
33194
  break;
@@ -49650,11 +49690,16 @@ COMMANDS
49650
49690
  lotics app workflow pull Rewrite src/workflows/*.ts from the server
49651
49691
  lotics app workflow check [alias] Typecheck src/workflows bodies locally (one
49652
49692
  isolated program per alias; the app's own tsc)
49693
+ lotics app query set <alias> Push package.json#lotics.queries.<alias> to
49694
+ apps.queries via set_app_query (no deploy;
49695
+ re-synced by the next deploy from the manifest)
49653
49696
  lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
49654
49697
  lotics app rename "<new name>" Rename the app's display name (launcher title)
49655
49698
  lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
49656
- lotics ui link <component> [--remove] Dev-link @lotics/ui to the monorepo's
49657
- packages/ui/src for live HMR (monorepo only)
49699
+ lotics ui link <component> [--ui-src <path>] [--remove]
49700
+ Dev-link @lotics/ui to packages/ui/src (Vite alias
49701
+ + tsc paths) for live HMR + typecheck. Monorepo apps
49702
+ auto-find it; external apps pass --ui-src / LOTICS_UI_SRC
49658
49703
  lotics xlsx <subcommand> ... Read/write/edit .xlsx files on your local filesystem
49659
49704
  (uses the bundled Lotics xlsx engine; prefer over
49660
49705
  npm xlsx/exceljs for round-trip fidelity)
@@ -50018,15 +50063,16 @@ async function main() {
50018
50063
  if (subcommand === "link") {
50019
50064
  const component = toolArgs;
50020
50065
  if (!component) {
50021
- console.error("Usage: lotics ui link <component> [--remove]");
50022
- console.error("Dev-links @lotics/ui to the monorepo's packages/ui/src for live HMR.");
50066
+ console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
50067
+ console.error("Dev-links @lotics/ui to packages/ui/src (Vite + tsc) for live HMR + typecheck.");
50068
+ console.error("Monorepo apps find packages/ui/src automatically; external apps pass --ui-src / LOTICS_UI_SRC.");
50023
50069
  process.exit(1);
50024
50070
  }
50025
- appUiLink({ component, remove: restArgs.includes("--remove") });
50071
+ appUiLink({ component, uiSrc: flags.uiSrc, remove: restArgs.includes("--remove") });
50026
50072
  return;
50027
50073
  }
50028
50074
  console.error(`Unknown ui subcommand: ${subcommand ?? "(none)"}`);
50029
- console.error("Usage: lotics ui link <component> [--remove]");
50075
+ console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
50030
50076
  process.exit(1);
50031
50077
  }
50032
50078
  if (command === "app" && subcommand === "workflow" && toolArgs === "check") {
@@ -50119,6 +50165,7 @@ async function main() {
50119
50165
  console.error(" lotics app workflow set <alias> Push the edited src/workflows/<alias>.ts body");
50120
50166
  console.error(" lotics app workflow pull Rewrite src/workflows/*.ts from the server");
50121
50167
  console.error(" lotics app workflow check [alias] Typecheck src/workflows bodies locally");
50168
+ console.error(" lotics app query set <alias> Push lotics.queries.<alias> to apps.queries (no deploy)");
50122
50169
  console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
50123
50170
  console.error(` lotics app rename "<new name>" Rename the app's display name (launcher title)`);
50124
50171
  console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
@@ -50324,6 +50371,26 @@ Available workspaces:`);
50324
50371
  }
50325
50372
  workflowUsage();
50326
50373
  }
50374
+ if (subcommand === "query") {
50375
+ const action = toolArgs;
50376
+ if (action === "set") {
50377
+ const alias = restArgs[0];
50378
+ if (!alias) {
50379
+ console.error("Usage: lotics app query set <alias>");
50380
+ console.error(
50381
+ "Pushes package.json#lotics.queries.<alias> to apps.queries via set_app_query (no deploy)."
50382
+ );
50383
+ process.exit(1);
50384
+ }
50385
+ await appQuerySet(client, { alias });
50386
+ return;
50387
+ }
50388
+ console.error("Usage:");
50389
+ console.error(
50390
+ " lotics app query set <alias> Push package.json#lotics.queries.<alias> to apps.queries (no deploy)"
50391
+ );
50392
+ process.exit(1);
50393
+ }
50327
50394
  if (subcommand === "dev") {
50328
50395
  const projectDir = toolArgs;
50329
50396
  let port;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.65.0",
3
+ "version": "0.66.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {