@lotics/cli 0.45.2 → 0.47.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.
@@ -102,6 +102,12 @@ export declare function appPull(client: LoticsClient, args: {
102
102
  app_id: string;
103
103
  targetPath?: string;
104
104
  }): Promise<void>;
105
+ /**
106
+ * Capabilities the source CALLS but the manifest does not DECLARE — each one
107
+ * silently 403s at runtime (GAP-29). Pure over the concatenated source text so
108
+ * it's unit-testable; the deploy warns (non-blocking) on a non-empty result.
109
+ */
110
+ export declare function undeclaredCapabilities(sourceText: string, declared: Record<string, boolean | undefined> | undefined): string[];
105
111
  /**
106
112
  * `lotics app deploy [-m <message>]`
107
113
  *
@@ -113,7 +119,6 @@ export declare function appPull(client: LoticsClient, args: {
113
119
  export declare function appDeploy(client: LoticsClient, args: {
114
120
  projectDir?: string;
115
121
  message?: string;
116
- forceWorkflowSync?: boolean;
117
122
  }): Promise<void>;
118
123
  /**
119
124
  * `lotics app dev [path] [--port=5174] [--vite-port=5173]`
@@ -289,6 +289,48 @@ export async function appPull(client, args) {
289
289
  console.error(` # edit src/App.tsx`);
290
290
  console.error(` lotics app deploy`);
291
291
  }
292
+ /**
293
+ * SDK calls that only work when the matching capability is declared in
294
+ * `package.json#lotics.capabilities`. Used by the deploy pre-flight (GAP-29):
295
+ * code that calls one of these but omits the capability silently 403s at
296
+ * runtime, with no build or deploy error. Extend this map when a new capability
297
+ * gate ships in `@lotics/app-sdk`.
298
+ */
299
+ const CAPABILITY_GATED_CALLS = {
300
+ comments: ["useComments", "createComment", "updateComment", "deleteComment"],
301
+ };
302
+ /**
303
+ * Capabilities the source CALLS but the manifest does not DECLARE — each one
304
+ * silently 403s at runtime (GAP-29). Pure over the concatenated source text so
305
+ * it's unit-testable; the deploy warns (non-blocking) on a non-empty result.
306
+ */
307
+ export function undeclaredCapabilities(sourceText, declared) {
308
+ const used = [];
309
+ for (const [capability, calls] of Object.entries(CAPABILITY_GATED_CALLS)) {
310
+ const isCalled = calls.some((call) => new RegExp(`\\b${call}\\b`).test(sourceText));
311
+ if (isCalled && declared?.[capability] !== true)
312
+ used.push(capability);
313
+ }
314
+ return used;
315
+ }
316
+ /** Concatenated text of the app's `src/` files — the input to the capability pre-flight. */
317
+ function readAppSourceText(projectDir) {
318
+ const srcDir = path.join(projectDir, "src");
319
+ if (!fs.existsSync(srcDir))
320
+ return "";
321
+ const parts = [];
322
+ const walk = (dir) => {
323
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
324
+ const full = path.join(dir, entry.name);
325
+ if (entry.isDirectory())
326
+ walk(full);
327
+ else if (/\.(ts|tsx|js|jsx)$/.test(entry.name))
328
+ parts.push(fs.readFileSync(full, "utf8"));
329
+ }
330
+ };
331
+ walk(srcDir);
332
+ return parts.join("\n");
333
+ }
292
334
  /**
293
335
  * `lotics app deploy [-m <message>]`
294
336
  *
@@ -300,6 +342,16 @@ export async function appPull(client, args) {
300
342
  export async function appDeploy(client, args) {
301
343
  const projectDir = path.resolve(args.projectDir ?? process.cwd());
302
344
  const meta = readAppMeta(projectDir);
345
+ // Pre-flight (GAP-29): a capability the code calls but the manifest doesn't
346
+ // declare silently 403s at runtime with no other signal — warn before shipping
347
+ // it. Non-blocking, like the unbranded nudge below.
348
+ const undeclared = undeclaredCapabilities(readAppSourceText(projectDir), meta.capabilities);
349
+ if (undeclared.length > 0) {
350
+ const block = JSON.stringify(Object.fromEntries(undeclared.map((c) => [c, true])));
351
+ console.error(`\n⚠ This app calls capability-gated SDK functions for ${undeclared.join(", ")} but the ` +
352
+ `manifest doesn't declare ${undeclared.length > 1 ? "them" : "it"} — those calls will 403 at runtime.\n` +
353
+ ` Add to package.json#lotics.capabilities: ${block}`);
354
+ }
303
355
  // Regenerate AppWorkflows typing before the build picks up source. Keeps
304
356
  // .lotics/app_workflows.d.ts in sync with the manifest's workflows map
305
357
  // every time the developer ships.
@@ -336,20 +388,17 @@ export async function appDeploy(client, args) {
336
388
  dist_archive: fs.readFileSync(tmpDist),
337
389
  prev_version_id: meta.current_version_id,
338
390
  message: args.message,
339
- // Sync apps.workflows from the manifest. Server validates each
340
- // workflow_id exists in the workspace before committing.
341
- workflows: meta.workflows ?? {},
342
391
  // Sync apps.queries from the manifest. Server validates each query
343
392
  // template (parseQueryNode, table access, param coverage).
344
393
  queries: meta.queries ?? {},
345
- // Capabilities are manifest-authoritative (like workflows/queries):
346
- // always send, defaulting to `{}` when the manifest declares none — so
347
- // deleting the `capabilities` block turns every capability OFF on the
348
- // next deploy (fail-safe; the declaration is the grant).
394
+ // Capabilities are manifest-authoritative (like queries): always send,
395
+ // defaulting to `{}` when the manifest declares none — so deleting the
396
+ // `capabilities` block turns every capability OFF on the next deploy
397
+ // (fail-safe; the declaration is the grant).
349
398
  capabilities: meta.capabilities ?? {},
350
- // Opt into destructive workflow removal. Default falsethe server
351
- // rejects deploys whose manifest is missing aliases the App row has.
352
- force_workflow_sync: args.forceWorkflowSync,
399
+ // Workflow bindings are NOT a deploy concernset_app_workflow /
400
+ // remove_app_workflow own apps.workflows. The manifest's `workflows`
401
+ // map is a pulled reflection used only for the .d.ts codegen above.
353
402
  });
354
403
  writeAppMeta(projectDir, {
355
404
  ...meta,
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { tmpdir } from "node:os";
5
- import { stampPulledManifest } from "./app_commands.js";
5
+ import { stampPulledManifest, undeclaredCapabilities } from "./app_commands.js";
6
6
  /**
7
7
  * `appPull` reads workflows from the live App row (server response), NOT from
8
8
  * the manifest embedded in the extracted source archive. The frozen archive
@@ -104,3 +104,31 @@ describe("stampPulledManifest", () => {
104
104
  expect(dts).toContain("only_live");
105
105
  });
106
106
  });
107
+ /**
108
+ * The deploy pre-flight that catches GAP-29: source that CALLS a capability-gated
109
+ * SDK function while the manifest omits that capability would silently 403 at
110
+ * runtime. `undeclaredCapabilities` is the pure decision the warning is built on.
111
+ */
112
+ describe("undeclaredCapabilities", () => {
113
+ const commentsSource = `import { useComments } from "@lotics/app-sdk";\nconst { available } = useComments({ record_id });`;
114
+ it("flags a capability that's called but not declared", () => {
115
+ expect(undeclaredCapabilities(commentsSource, undefined)).toEqual(["comments"]);
116
+ expect(undeclaredCapabilities(commentsSource, {})).toEqual(["comments"]);
117
+ expect(undeclaredCapabilities(commentsSource, { comments: false })).toEqual(["comments"]);
118
+ });
119
+ it("stays quiet when the called capability is declared", () => {
120
+ expect(undeclaredCapabilities(commentsSource, { comments: true })).toEqual([]);
121
+ });
122
+ it("stays quiet when the capability isn't used at all", () => {
123
+ expect(undeclaredCapabilities(`const x = createInvoice();`, undefined)).toEqual([]);
124
+ expect(undeclaredCapabilities("", { comments: true })).toEqual([]);
125
+ });
126
+ it("matches on whole identifiers, not substrings", () => {
127
+ // `myUseCommentsHelper` is not a call to the gated `useComments`.
128
+ expect(undeclaredCapabilities(`const x = myUseCommentsHelper();`, undefined)).toEqual([]);
129
+ });
130
+ it("detects any of a capability's gated calls", () => {
131
+ expect(undeclaredCapabilities(`await createComment({ body });`, {})).toEqual(["comments"]);
132
+ expect(undeclaredCapabilities(`await deleteComment(id);`, {})).toEqual(["comments"]);
133
+ });
134
+ });
package/dist/args.d.ts CHANGED
@@ -4,8 +4,8 @@
4
4
  * Splits `process.argv.slice(2)` into a command / subcommand / positional
5
5
  * (`toolArgs`) / remaining-positionals (`restArgs`) shape plus typed `flags`.
6
6
  * Value-taking flags (`-m`, `--api-key`, …) consume the next token; boolean
7
- * flags (`--force-workflow-sync`, `--json`, …) toggle. Anything not matching a
8
- * known flag is positional.
7
+ * flags (`--json`, `--local`, …) toggle. Anything not matching a known flag is
8
+ * positional.
9
9
  *
10
10
  * Lives apart from `cli.ts` so it can be unit-tested — `cli.ts` runs `main()`
11
11
  * on import, so importing the parser from there would execute the CLI.
@@ -26,7 +26,6 @@ export declare function parseArgs(argv: string[]): {
26
26
  name?: string;
27
27
  timezone?: string;
28
28
  message?: string;
29
- forceWorkflowSync: boolean;
30
29
  local: boolean;
31
30
  all: boolean;
32
31
  version: boolean;
package/dist/args.js CHANGED
@@ -4,8 +4,8 @@
4
4
  * Splits `process.argv.slice(2)` into a command / subcommand / positional
5
5
  * (`toolArgs`) / remaining-positionals (`restArgs`) shape plus typed `flags`.
6
6
  * Value-taking flags (`-m`, `--api-key`, …) consume the next token; boolean
7
- * flags (`--force-workflow-sync`, `--json`, …) toggle. Anything not matching a
8
- * known flag is positional.
7
+ * flags (`--json`, `--local`, …) toggle. Anything not matching a known flag is
8
+ * positional.
9
9
  *
10
10
  * Lives apart from `cli.ts` so it can be unit-tested — `cli.ts` runs `main()`
11
11
  * on import, so importing the parser from there would execute the CLI.
@@ -22,7 +22,6 @@ export function parseArgs(argv) {
22
22
  name: undefined,
23
23
  timezone: undefined,
24
24
  message: undefined,
25
- forceWorkflowSync: false,
26
25
  local: false,
27
26
  all: false,
28
27
  version: false,
@@ -69,9 +68,6 @@ export function parseArgs(argv) {
69
68
  case "--message":
70
69
  flags.message = argv[++i];
71
70
  break;
72
- case "--force-workflow-sync":
73
- flags.forceWorkflowSync = true;
74
- break;
75
71
  case "--local":
76
72
  flags.local = true;
77
73
  break;
package/dist/args.test.js CHANGED
@@ -8,8 +8,8 @@ describe("parseArgs", () => {
8
8
  expect(r.toolArgs).toBe("my message");
9
9
  expect(r.restArgs).toEqual([]);
10
10
  });
11
- // `app deploy` regression: `-m` and `--force-workflow-sync` must be parsed
12
- // as flags, not silently consumed as the positional message.
11
+ // `app deploy` regression: `-m` must be parsed as a flag, not silently
12
+ // consumed as the positional message.
13
13
  it("parses -m as the message flag", () => {
14
14
  const r = parseArgs(["app", "deploy", "-m", "a message"]);
15
15
  expect(r.flags.message).toBe("a message");
@@ -19,19 +19,8 @@ describe("parseArgs", () => {
19
19
  const r = parseArgs(["app", "deploy", "--message", "a message"]);
20
20
  expect(r.flags.message).toBe("a message");
21
21
  });
22
- it("parses --force-workflow-sync as a boolean flag", () => {
23
- const r = parseArgs(["app", "deploy", "msg", "--force-workflow-sync"]);
24
- expect(r.flags.forceWorkflowSync).toBe(true);
25
- expect(r.toolArgs).toBe("msg");
26
- });
27
- it("parses --force-workflow-sync before -m without eating the message", () => {
28
- const r = parseArgs(["app", "deploy", "--force-workflow-sync", "-m", "msg"]);
29
- expect(r.flags.forceWorkflowSync).toBe(true);
30
- expect(r.flags.message).toBe("msg");
31
- });
32
- it("defaults forceWorkflowSync to false and message to undefined", () => {
22
+ it("defaults message to undefined for a bare deploy", () => {
33
23
  const r = parseArgs(["app", "deploy"]);
34
- expect(r.flags.forceWorkflowSync).toBe(false);
35
24
  expect(r.flags.message).toBeUndefined();
36
25
  });
37
26
  it("parses --workspace as a value flag", () => {
package/dist/cli.js CHANGED
@@ -60,10 +60,9 @@ COMMANDS
60
60
  Download all files on a record file field
61
61
  lotics app create <name> [path] Create a new custom-code app + scaffold locally
62
62
  lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)
63
- lotics app deploy [-m <message>] [--force-workflow-sync]
64
- Build + upload current dir as a new version
65
- (--force-workflow-sync wipes server-only
66
- workflow aliases; default refuses)
63
+ lotics app deploy [-m <message>] Build + upload current dir as a new version
64
+ (code + queries only workflow bindings are
65
+ managed by set_app_workflow / remove_app_workflow)
67
66
  lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
68
67
  lotics app rename "<new name>" Rename the app's display name (launcher title)
69
68
  lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
@@ -527,8 +526,7 @@ async function main() {
527
526
  console.error("Usage:");
528
527
  console.error(" lotics app create <name> [path] Scaffold a new app locally");
529
528
  console.error(" lotics app pull <app_id> [path] Pull an existing app for local editing");
530
- console.error(" lotics app deploy [-m <message>] [--force-workflow-sync]");
531
- console.error(" Build + upload the current directory");
529
+ console.error(" lotics app deploy [-m <message>] Build + upload the current directory");
532
530
  console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
533
531
  console.error(" lotics app rename \"<new name>\" Rename the app's display name (launcher title)");
534
532
  console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
@@ -650,11 +648,9 @@ async function main() {
650
648
  return;
651
649
  }
652
650
  if (subcommand === "deploy") {
653
- // The message is either `-m <message>` or a bare positional arg after
654
- // `deploy`. `--force-workflow-sync` is a parsed boolean flag, valid in
655
- // any position.
651
+ // The message is either `-m <message>` or a bare positional arg after `deploy`.
656
652
  const message = flags.message ?? toolArgs;
657
- await appDeploy(client, { message, forceWorkflowSync: flags.forceWorkflowSync });
653
+ await appDeploy(client, { message });
658
654
  return;
659
655
  }
660
656
  if (subcommand === "subdomain") {
package/dist/client.d.ts CHANGED
@@ -235,19 +235,6 @@ export declare class LoticsClient {
235
235
  dist_archive: Buffer;
236
236
  prev_version_id?: string | null;
237
237
  message?: string | null;
238
- /**
239
- * Alias → workflow declaration map from the app's `package.json#lotics.workflows`.
240
- * Each value is a `{ workflow_id, inputs? }` object — inputs declares a typed
241
- * schema or is omitted when the workflow accepts no typed inputs. Always sent
242
- * (empty object when none declared) so the server can overwrite apps.workflows
243
- * authoritatively. Deleting an alias from the manifest removes it from the DB
244
- * on next deploy — gated by the destructive-removal guard unless
245
- * `force_workflow_sync` is true.
246
- */
247
- workflows?: Record<string, {
248
- workflow_id: string;
249
- inputs?: Record<string, unknown>;
250
- }>;
251
238
  /**
252
239
  * Alias → query declaration map from `package.json#lotics.queries`. Each
253
240
  * value is `{ ast, params? }` — a fixed query AST template and an optional
@@ -267,14 +254,6 @@ export declare class LoticsClient {
267
254
  capabilities?: {
268
255
  comments?: boolean;
269
256
  };
270
- /**
271
- * Opt into destructive workflow removal. When false/absent, the server
272
- * rejects a deploy whose `workflows` map is missing aliases that exist
273
- * on the App row. Set true to deploy anyway and wipe the missing aliases —
274
- * the user is asserting they know what they're doing. Wired to CLI flag
275
- * `--force-workflow-sync`.
276
- */
277
- force_workflow_sync?: boolean;
278
257
  }): Promise<{
279
258
  version_id: string;
280
259
  version_number: number;
package/dist/client.js CHANGED
@@ -239,11 +239,9 @@ export class LoticsClient {
239
239
  if (args.message) {
240
240
  formData.append("message", args.message);
241
241
  }
242
- // Always send workflows — empty object is meaningful (clears any
243
- // previously-declared aliases). Server validates each entry.
244
- formData.append("workflows", JSON.stringify(args.workflows ?? {}));
245
242
  // Always send queries — empty object clears any previously-declared
246
- // named queries. Server validates each template.
243
+ // named queries. Server validates each template. (Workflow bindings are
244
+ // not sent: set_app_workflow / remove_app_workflow own apps.workflows.)
247
245
  formData.append("queries", JSON.stringify(args.queries ?? {}));
248
246
  // capabilities is manifest-authoritative — the caller always passes it
249
247
  // (`{}` when none declared), so a deploy turns off any capability the
@@ -251,12 +249,6 @@ export class LoticsClient {
251
249
  if (args.capabilities !== undefined) {
252
250
  formData.append("capabilities", JSON.stringify(args.capabilities));
253
251
  }
254
- // Only post the override flag when the user explicitly opts in. The
255
- // server defaults to "honor the guard" — opt-in is loud, opt-out
256
- // requires intent.
257
- if (args.force_workflow_sync) {
258
- formData.append("force_workflow_sync", "true");
259
- }
260
252
  const url = `${this.baseUrl}/v1/apps/${encodeURIComponent(args.app_id)}/versions`;
261
253
  const response = await fetch(url, {
262
254
  method: "POST",
package/dist/src/cli.js CHANGED
@@ -29858,14 +29858,10 @@ var LoticsClient = class {
29858
29858
  if (args.message) {
29859
29859
  formData.append("message", args.message);
29860
29860
  }
29861
- formData.append("workflows", JSON.stringify(args.workflows ?? {}));
29862
29861
  formData.append("queries", JSON.stringify(args.queries ?? {}));
29863
29862
  if (args.capabilities !== void 0) {
29864
29863
  formData.append("capabilities", JSON.stringify(args.capabilities));
29865
29864
  }
29866
- if (args.force_workflow_sync) {
29867
- formData.append("force_workflow_sync", "true");
29868
- }
29869
29865
  const url = `${this.baseUrl}/v1/apps/${encodeURIComponent(args.app_id)}/versions`;
29870
29866
  const response = await fetch(url, {
29871
29867
  method: "POST",
@@ -31570,9 +31566,43 @@ Ready. Next steps:`);
31570
31566
  console.error(` # edit src/App.tsx`);
31571
31567
  console.error(` lotics app deploy`);
31572
31568
  }
31569
+ var CAPABILITY_GATED_CALLS = {
31570
+ comments: ["useComments", "createComment", "updateComment", "deleteComment"]
31571
+ };
31572
+ function undeclaredCapabilities(sourceText, declared) {
31573
+ const used = [];
31574
+ for (const [capability, calls] of Object.entries(CAPABILITY_GATED_CALLS)) {
31575
+ const isCalled = calls.some((call) => new RegExp(`\\b${call}\\b`).test(sourceText));
31576
+ if (isCalled && declared?.[capability] !== true) used.push(capability);
31577
+ }
31578
+ return used;
31579
+ }
31580
+ function readAppSourceText(projectDir) {
31581
+ const srcDir = path4.join(projectDir, "src");
31582
+ if (!fs3.existsSync(srcDir)) return "";
31583
+ const parts = [];
31584
+ const walk = (dir) => {
31585
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
31586
+ const full = path4.join(dir, entry.name);
31587
+ if (entry.isDirectory()) walk(full);
31588
+ else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) parts.push(fs3.readFileSync(full, "utf8"));
31589
+ }
31590
+ };
31591
+ walk(srcDir);
31592
+ return parts.join("\n");
31593
+ }
31573
31594
  async function appDeploy(client, args) {
31574
31595
  const projectDir = path4.resolve(args.projectDir ?? process.cwd());
31575
31596
  const meta = readAppMeta(projectDir);
31597
+ const undeclared = undeclaredCapabilities(readAppSourceText(projectDir), meta.capabilities);
31598
+ if (undeclared.length > 0) {
31599
+ const block = JSON.stringify(Object.fromEntries(undeclared.map((c) => [c, true])));
31600
+ console.error(
31601
+ `
31602
+ \u26A0 This app calls capability-gated SDK functions for ${undeclared.join(", ")} but the manifest doesn't declare ${undeclared.length > 1 ? "them" : "it"} \u2014 those calls will 403 at runtime.
31603
+ Add to package.json#lotics.capabilities: ${block}`
31604
+ );
31605
+ }
31576
31606
  writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries });
31577
31607
  console.error("Building...");
31578
31608
  await runNpm(["run", "build"], projectDir);
@@ -31609,20 +31639,17 @@ async function appDeploy(client, args) {
31609
31639
  dist_archive: fs3.readFileSync(tmpDist),
31610
31640
  prev_version_id: meta.current_version_id,
31611
31641
  message: args.message,
31612
- // Sync apps.workflows from the manifest. Server validates each
31613
- // workflow_id exists in the workspace before committing.
31614
- workflows: meta.workflows ?? {},
31615
31642
  // Sync apps.queries from the manifest. Server validates each query
31616
31643
  // template (parseQueryNode, table access, param coverage).
31617
31644
  queries: meta.queries ?? {},
31618
- // Capabilities are manifest-authoritative (like workflows/queries):
31619
- // always send, defaulting to `{}` when the manifest declares none — so
31620
- // deleting the `capabilities` block turns every capability OFF on the
31621
- // next deploy (fail-safe; the declaration is the grant).
31622
- capabilities: meta.capabilities ?? {},
31623
- // Opt into destructive workflow removal. Default falsethe server
31624
- // rejects deploys whose manifest is missing aliases the App row has.
31625
- force_workflow_sync: args.forceWorkflowSync
31645
+ // Capabilities are manifest-authoritative (like queries): always send,
31646
+ // defaulting to `{}` when the manifest declares none — so deleting the
31647
+ // `capabilities` block turns every capability OFF on the next deploy
31648
+ // (fail-safe; the declaration is the grant).
31649
+ capabilities: meta.capabilities ?? {}
31650
+ // Workflow bindings are NOT a deploy concernset_app_workflow /
31651
+ // remove_app_workflow own apps.workflows. The manifest's `workflows`
31652
+ // map is a pulled reflection used only for the .d.ts codegen above.
31626
31653
  });
31627
31654
  writeAppMeta(projectDir, {
31628
31655
  ...meta,
@@ -31722,7 +31749,6 @@ function parseArgs(argv) {
31722
31749
  name: void 0,
31723
31750
  timezone: void 0,
31724
31751
  message: void 0,
31725
- forceWorkflowSync: false,
31726
31752
  local: false,
31727
31753
  all: false,
31728
31754
  version: false,
@@ -31769,9 +31795,6 @@ function parseArgs(argv) {
31769
31795
  case "--message":
31770
31796
  flags.message = argv[++i2];
31771
31797
  break;
31772
- case "--force-workflow-sync":
31773
- flags.forceWorkflowSync = true;
31774
- break;
31775
31798
  case "--local":
31776
31799
  flags.local = true;
31777
31800
  break;
@@ -47561,10 +47584,9 @@ COMMANDS
47561
47584
  Download all files on a record file field
47562
47585
  lotics app create <name> [path] Create a new custom-code app + scaffold locally
47563
47586
  lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)
47564
- lotics app deploy [-m <message>] [--force-workflow-sync]
47565
- Build + upload current dir as a new version
47566
- (--force-workflow-sync wipes server-only
47567
- workflow aliases; default refuses)
47587
+ lotics app deploy [-m <message>] Build + upload current dir as a new version
47588
+ (code + queries only \u2014 workflow bindings are
47589
+ managed by set_app_workflow / remove_app_workflow)
47568
47590
  lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
47569
47591
  lotics app rename "<new name>" Rename the app's display name (launcher title)
47570
47592
  lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
@@ -47993,8 +48015,7 @@ async function main() {
47993
48015
  console.error("Usage:");
47994
48016
  console.error(" lotics app create <name> [path] Scaffold a new app locally");
47995
48017
  console.error(" lotics app pull <app_id> [path] Pull an existing app for local editing");
47996
- console.error(" lotics app deploy [-m <message>] [--force-workflow-sync]");
47997
- console.error(" Build + upload the current directory");
48018
+ console.error(" lotics app deploy [-m <message>] Build + upload the current directory");
47998
48019
  console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
47999
48020
  console.error(` lotics app rename "<new name>" Rename the app's display name (launcher title)`);
48000
48021
  console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
@@ -48111,7 +48132,7 @@ Available workspaces:`);
48111
48132
  }
48112
48133
  if (subcommand === "deploy") {
48113
48134
  const message = flags.message ?? toolArgs;
48114
- await appDeploy(client, { message, forceWorkflowSync: flags.forceWorkflowSync });
48135
+ await appDeploy(client, { message });
48115
48136
  return;
48116
48137
  }
48117
48138
  if (subcommand === "subdomain") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.45.2",
3
+ "version": "0.47.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {