@lotics/cli 0.46.0 → 0.48.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.
@@ -119,7 +119,6 @@ export declare function undeclaredCapabilities(sourceText: string, declared: Rec
119
119
  export declare function appDeploy(client: LoticsClient, args: {
120
120
  projectDir?: string;
121
121
  message?: string;
122
- forceWorkflowSync?: boolean;
123
122
  }): Promise<void>;
124
123
  /**
125
124
  * `lotics app dev [path] [--port=5174] [--vite-port=5173]`
@@ -388,20 +388,17 @@ export async function appDeploy(client, args) {
388
388
  dist_archive: fs.readFileSync(tmpDist),
389
389
  prev_version_id: meta.current_version_id,
390
390
  message: args.message,
391
- // Sync apps.workflows from the manifest. Server validates each
392
- // workflow_id exists in the workspace before committing.
393
- workflows: meta.workflows ?? {},
394
391
  // Sync apps.queries from the manifest. Server validates each query
395
392
  // template (parseQueryNode, table access, param coverage).
396
393
  queries: meta.queries ?? {},
397
- // Capabilities are manifest-authoritative (like workflows/queries):
398
- // always send, defaulting to `{}` when the manifest declares none — so
399
- // deleting the `capabilities` block turns every capability OFF on the
400
- // 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).
401
398
  capabilities: meta.capabilities ?? {},
402
- // Opt into destructive workflow removal. Default falsethe server
403
- // rejects deploys whose manifest is missing aliases the App row has.
404
- 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.
405
402
  });
406
403
  writeAppMeta(projectDir, {
407
404
  ...meta,
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
@@ -183,6 +183,21 @@ export declare class LoticsClient {
183
183
  image: string | null;
184
184
  }>;
185
185
  }>;
186
+ /**
187
+ * Resolve the full option set (key, label, color) of a named query's select
188
+ * columns — the picker companion to `appQuery`. Mirrors
189
+ * POST /v1/apps/{app_id}/field-options.
190
+ */
191
+ appFieldOptions(app_id: string, alias: string): Promise<{
192
+ fields: Record<string, {
193
+ label: string;
194
+ options: Array<{
195
+ key: string;
196
+ label: string;
197
+ color: string;
198
+ }>;
199
+ }>;
200
+ }>;
186
201
  /**
187
202
  * Execute a workflow by alias declared in package.json#lotics.workflows.
188
203
  * Mirrors POST /v1/apps/{app_id}/workflows/{alias}/execute.
@@ -235,19 +250,6 @@ export declare class LoticsClient {
235
250
  dist_archive: Buffer;
236
251
  prev_version_id?: string | null;
237
252
  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
253
  /**
252
254
  * Alias → query declaration map from `package.json#lotics.queries`. Each
253
255
  * value is `{ ast, params? }` — a fixed query AST template and an optional
@@ -267,14 +269,6 @@ export declare class LoticsClient {
267
269
  capabilities?: {
268
270
  comments?: boolean;
269
271
  };
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
272
  }): Promise<{
279
273
  version_id: string;
280
274
  version_number: number;
package/dist/client.js CHANGED
@@ -187,6 +187,14 @@ export class LoticsClient {
187
187
  const qs = group_id ? `?group_id=${encodeURIComponent(group_id)}` : "";
188
188
  return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/members${qs}`);
189
189
  }
190
+ /**
191
+ * Resolve the full option set (key, label, color) of a named query's select
192
+ * columns — the picker companion to `appQuery`. Mirrors
193
+ * POST /v1/apps/{app_id}/field-options.
194
+ */
195
+ async appFieldOptions(app_id, alias) {
196
+ return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/field-options`, { alias });
197
+ }
190
198
  /**
191
199
  * Execute a workflow by alias declared in package.json#lotics.workflows.
192
200
  * Mirrors POST /v1/apps/{app_id}/workflows/{alias}/execute.
@@ -239,11 +247,9 @@ export class LoticsClient {
239
247
  if (args.message) {
240
248
  formData.append("message", args.message);
241
249
  }
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
250
  // Always send queries — empty object clears any previously-declared
246
- // named queries. Server validates each template.
251
+ // named queries. Server validates each template. (Workflow bindings are
252
+ // not sent: set_app_workflow / remove_app_workflow own apps.workflows.)
247
253
  formData.append("queries", JSON.stringify(args.queries ?? {}));
248
254
  // capabilities is manifest-authoritative — the caller always passes it
249
255
  // (`{}` when none declared), so a deploy turns off any capability the
@@ -251,12 +257,6 @@ export class LoticsClient {
251
257
  if (args.capabilities !== undefined) {
252
258
  formData.append("capabilities", JSON.stringify(args.capabilities));
253
259
  }
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
260
  const url = `${this.baseUrl}/v1/apps/${encodeURIComponent(args.app_id)}/versions`;
261
261
  const response = await fetch(url, {
262
262
  method: "POST",
@@ -6,7 +6,7 @@
6
6
  * { message }. Same shape as the production iframe-host's error path.
7
7
  */
8
8
  import { LoticsClient } from "../client.js";
9
- export type RpcOp = "query" | "workflow" | "members" | "context" | "upload_url" | "upload_complete" | "comments.list" | "comments.create" | "comments.update" | "comments.delete" | "comments.counts";
9
+ export type RpcOp = "query" | "field_options" | "workflow" | "members" | "context" | "upload_url" | "upload_complete" | "comments.list" | "comments.create" | "comments.update" | "comments.delete" | "comments.counts";
10
10
  export interface RpcRequest {
11
11
  app_id: string;
12
12
  op: RpcOp;
@@ -7,6 +7,7 @@
7
7
  */
8
8
  const SUPPORTED_OPS = new Set([
9
9
  "query",
10
+ "field_options",
10
11
  "workflow",
11
12
  "members",
12
13
  "context",
@@ -52,6 +53,13 @@ export async function dispatchRpc(client, body, opts) {
52
53
  count: p.count,
53
54
  });
54
55
  }
56
+ case "field_options": {
57
+ const p = body.payload;
58
+ if (!p || typeof p.alias !== "string") {
59
+ throw new Error("field_options payload must include `alias`");
60
+ }
61
+ return client.appFieldOptions(body.app_id, p.alias);
62
+ }
55
63
  case "workflow": {
56
64
  const p = body.payload;
57
65
  if (!p || typeof p.alias !== "string") {
@@ -27,6 +27,27 @@ describe("dispatchRpc — context op", () => {
27
27
  await expect(dispatchRpc(client, { app_id: "app_x", op: "bogus", payload: {} })).rejects.toThrow(/Unknown RPC op/);
28
28
  });
29
29
  });
30
+ describe("dispatchRpc — field_options op", () => {
31
+ it("forwards the alias to appFieldOptions and returns its { fields } shape", async () => {
32
+ const fields = {
33
+ status: {
34
+ label: "Status",
35
+ options: [{ key: "opt_open", label: "Open", color: "blue" }],
36
+ },
37
+ };
38
+ const client = mockClient({ appFieldOptions: async () => ({ fields }) });
39
+ const result = await dispatchRpc(client, {
40
+ app_id: "app_x",
41
+ op: "field_options",
42
+ payload: { alias: "records" },
43
+ });
44
+ expect(result).toEqual({ fields });
45
+ });
46
+ it("requires an alias in the payload", async () => {
47
+ const client = mockClient({ appFieldOptions: async () => ({ fields: {} }) });
48
+ await expect(dispatchRpc(client, { app_id: "app_x", op: "field_options", payload: {} })).rejects.toThrow(/alias/);
49
+ });
50
+ });
30
51
  describe("dispatchRpc — comment ops", () => {
31
52
  it("comments.list forwards to appGetRecordComments, wrapped as { comments }", async () => {
32
53
  const comments = [{ id: "cmt_1", content: "hi" }];
package/dist/src/cli.js CHANGED
@@ -29772,6 +29772,14 @@ var LoticsClient = class {
29772
29772
  const qs = group_id ? `?group_id=${encodeURIComponent(group_id)}` : "";
29773
29773
  return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/members${qs}`);
29774
29774
  }
29775
+ /**
29776
+ * Resolve the full option set (key, label, color) of a named query's select
29777
+ * columns — the picker companion to `appQuery`. Mirrors
29778
+ * POST /v1/apps/{app_id}/field-options.
29779
+ */
29780
+ async appFieldOptions(app_id, alias) {
29781
+ return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/field-options`, { alias });
29782
+ }
29775
29783
  /**
29776
29784
  * Execute a workflow by alias declared in package.json#lotics.workflows.
29777
29785
  * Mirrors POST /v1/apps/{app_id}/workflows/{alias}/execute.
@@ -29858,14 +29866,10 @@ var LoticsClient = class {
29858
29866
  if (args.message) {
29859
29867
  formData.append("message", args.message);
29860
29868
  }
29861
- formData.append("workflows", JSON.stringify(args.workflows ?? {}));
29862
29869
  formData.append("queries", JSON.stringify(args.queries ?? {}));
29863
29870
  if (args.capabilities !== void 0) {
29864
29871
  formData.append("capabilities", JSON.stringify(args.capabilities));
29865
29872
  }
29866
- if (args.force_workflow_sync) {
29867
- formData.append("force_workflow_sync", "true");
29868
- }
29869
29873
  const url = `${this.baseUrl}/v1/apps/${encodeURIComponent(args.app_id)}/versions`;
29870
29874
  const response = await fetch(url, {
29871
29875
  method: "POST",
@@ -30724,6 +30728,7 @@ import { spawn } from "node:child_process";
30724
30728
  // src/dev/rpc_handler.ts
30725
30729
  var SUPPORTED_OPS = /* @__PURE__ */ new Set([
30726
30730
  "query",
30731
+ "field_options",
30727
30732
  "workflow",
30728
30733
  "members",
30729
30734
  "context",
@@ -30765,6 +30770,13 @@ async function dispatchRpc(client, body, opts) {
30765
30770
  count: p.count
30766
30771
  });
30767
30772
  }
30773
+ case "field_options": {
30774
+ const p = body.payload;
30775
+ if (!p || typeof p.alias !== "string") {
30776
+ throw new Error("field_options payload must include `alias`");
30777
+ }
30778
+ return client.appFieldOptions(body.app_id, p.alias);
30779
+ }
30768
30780
  case "workflow": {
30769
30781
  const p = body.payload;
30770
30782
  if (!p || typeof p.alias !== "string") {
@@ -31643,20 +31655,17 @@ async function appDeploy(client, args) {
31643
31655
  dist_archive: fs3.readFileSync(tmpDist),
31644
31656
  prev_version_id: meta.current_version_id,
31645
31657
  message: args.message,
31646
- // Sync apps.workflows from the manifest. Server validates each
31647
- // workflow_id exists in the workspace before committing.
31648
- workflows: meta.workflows ?? {},
31649
31658
  // Sync apps.queries from the manifest. Server validates each query
31650
31659
  // template (parseQueryNode, table access, param coverage).
31651
31660
  queries: meta.queries ?? {},
31652
- // Capabilities are manifest-authoritative (like workflows/queries):
31653
- // always send, defaulting to `{}` when the manifest declares none — so
31654
- // deleting the `capabilities` block turns every capability OFF on the
31655
- // next deploy (fail-safe; the declaration is the grant).
31656
- capabilities: meta.capabilities ?? {},
31657
- // Opt into destructive workflow removal. Default falsethe server
31658
- // rejects deploys whose manifest is missing aliases the App row has.
31659
- force_workflow_sync: args.forceWorkflowSync
31661
+ // Capabilities are manifest-authoritative (like queries): always send,
31662
+ // defaulting to `{}` when the manifest declares none — so deleting the
31663
+ // `capabilities` block turns every capability OFF on the next deploy
31664
+ // (fail-safe; the declaration is the grant).
31665
+ capabilities: meta.capabilities ?? {}
31666
+ // Workflow bindings are NOT a deploy concernset_app_workflow /
31667
+ // remove_app_workflow own apps.workflows. The manifest's `workflows`
31668
+ // map is a pulled reflection used only for the .d.ts codegen above.
31660
31669
  });
31661
31670
  writeAppMeta(projectDir, {
31662
31671
  ...meta,
@@ -31756,7 +31765,6 @@ function parseArgs(argv) {
31756
31765
  name: void 0,
31757
31766
  timezone: void 0,
31758
31767
  message: void 0,
31759
- forceWorkflowSync: false,
31760
31768
  local: false,
31761
31769
  all: false,
31762
31770
  version: false,
@@ -31803,9 +31811,6 @@ function parseArgs(argv) {
31803
31811
  case "--message":
31804
31812
  flags.message = argv[++i2];
31805
31813
  break;
31806
- case "--force-workflow-sync":
31807
- flags.forceWorkflowSync = true;
31808
- break;
31809
31814
  case "--local":
31810
31815
  flags.local = true;
31811
31816
  break;
@@ -47595,10 +47600,9 @@ COMMANDS
47595
47600
  Download all files on a record file field
47596
47601
  lotics app create <name> [path] Create a new custom-code app + scaffold locally
47597
47602
  lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)
47598
- lotics app deploy [-m <message>] [--force-workflow-sync]
47599
- Build + upload current dir as a new version
47600
- (--force-workflow-sync wipes server-only
47601
- workflow aliases; default refuses)
47603
+ lotics app deploy [-m <message>] Build + upload current dir as a new version
47604
+ (code + queries only \u2014 workflow bindings are
47605
+ managed by set_app_workflow / remove_app_workflow)
47602
47606
  lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
47603
47607
  lotics app rename "<new name>" Rename the app's display name (launcher title)
47604
47608
  lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
@@ -48027,8 +48031,7 @@ async function main() {
48027
48031
  console.error("Usage:");
48028
48032
  console.error(" lotics app create <name> [path] Scaffold a new app locally");
48029
48033
  console.error(" lotics app pull <app_id> [path] Pull an existing app for local editing");
48030
- console.error(" lotics app deploy [-m <message>] [--force-workflow-sync]");
48031
- console.error(" Build + upload the current directory");
48034
+ console.error(" lotics app deploy [-m <message>] Build + upload the current directory");
48032
48035
  console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
48033
48036
  console.error(` lotics app rename "<new name>" Rename the app's display name (launcher title)`);
48034
48037
  console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
@@ -48145,7 +48148,7 @@ Available workspaces:`);
48145
48148
  }
48146
48149
  if (subcommand === "deploy") {
48147
48150
  const message = flags.message ?? toolArgs;
48148
- await appDeploy(client, { message, forceWorkflowSync: flags.forceWorkflowSync });
48151
+ await appDeploy(client, { message });
48149
48152
  return;
48150
48153
  }
48151
48154
  if (subcommand === "subdomain") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {