@lotics/cli 0.25.0 → 0.27.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.
@@ -11,6 +11,18 @@ export type AppWorkflowDeclaration = {
11
11
  workflow_id: string;
12
12
  inputs?: Record<string, unknown>;
13
13
  };
14
+ /**
15
+ * Manifest declaration for one named query:
16
+ * `"alias": { ast, params?: { key: { type, … } } }`
17
+ *
18
+ * `ast` is a fixed query AST template; `params` declares the typed value
19
+ * holes (`{{params.<name>}}`) the app fills via `useQuery(alias, params)`.
20
+ * Custom-code apps never send a raw AST — this declaration is the contract.
21
+ */
22
+ export type AppQueryDeclaration = {
23
+ ast: unknown;
24
+ params?: Record<string, unknown>;
25
+ };
14
26
  /**
15
27
  * Stamp the post-extraction manifest with server-authoritative meta. Called
16
28
  * by `appPull` after the source archive lands on disk.
@@ -29,6 +41,7 @@ export declare function stampPulledManifest(projectDir: string, args: {
29
41
  current_version_id: string;
30
42
  version_number: number;
31
43
  workflows: Record<string, AppWorkflowDeclaration>;
44
+ queries: Record<string, AppQueryDeclaration>;
32
45
  }): void;
33
46
  /**
34
47
  * `lotics app create <name> [path]`
@@ -86,8 +99,8 @@ export declare function appDeploy(client: LoticsClient, args: {
86
99
  * `lotics app dev [path] [--port=5174] [--vite-port=5173]`
87
100
  *
88
101
  * Local dev mode for iframe apps. Spawns Vite + a postMessage RPC forwarder
89
- * that bridges the iframe's hooks (useQuery, useMutate, useAction,
90
- * useWorkflow) to api.lotics.ai using the CLI's stored API key. Wrapper
102
+ * that bridges the iframe's hooks (useQuery, useWorkflow) to api.lotics.ai
103
+ * using the CLI's stored API key. Wrapper
91
104
  * iframe matches the production sandbox attributes exactly — null origin,
92
105
  * allow-scripts — so prod-equivalent runtime behavior surfaces in dev.
93
106
  *
@@ -18,6 +18,7 @@ import { tmpdir } from "node:os";
18
18
  import { buildStarterTemplate } from "./starter_template.js";
19
19
  import { startDevServer, openBrowser } from "./dev/server.js";
20
20
  import { generateAppWorkflowsDts } from "./generate_app_workflows_dts.js";
21
+ import { generateAppQueriesDts } from "./generate_app_queries_dts.js";
21
22
  /** Run `tar` and resolve when it exits cleanly. Throws with stderr on failure. */
22
23
  function runTar(args, cwd) {
23
24
  return new Promise((resolve, reject) => {
@@ -64,6 +65,7 @@ function readAppMeta(projectDir) {
64
65
  current_version_id: pkg.lotics.current_version_id ?? null,
65
66
  version_number: pkg.lotics.version_number ?? null,
66
67
  workflows: pkg.lotics.workflows ?? {},
68
+ queries: pkg.lotics.queries ?? {},
67
69
  };
68
70
  }
69
71
  function writeAppMeta(projectDir, meta) {
@@ -73,16 +75,17 @@ function writeAppMeta(projectDir, meta) {
73
75
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
74
76
  }
75
77
  /**
76
- * Write `.lotics/app_workflows.d.ts` from the manifest's `workflows` map.
77
- * Called from `app pull`, `app dev`, and `app deploy` so the augmented
78
- * `AppWorkflows` types stay in sync with the manifest. The starter
79
- * tsconfig.json includes `.lotics/` so tsc + Vite pick it up automatically.
78
+ * Write `.lotics/app_workflows.d.ts` + `.lotics/app_queries.d.ts` from the
79
+ * manifest's `workflows` / `queries` maps. Called from `app create / pull /
80
+ * dev / deploy` so the augmented `AppWorkflows` / `AppQueries` types stay in
81
+ * sync with the manifest. The starter tsconfig.json includes `.lotics/` so
82
+ * tsc + Vite pick the files up automatically.
80
83
  */
81
- function writeAppWorkflowsDts(projectDir, workflows) {
84
+ function writeAppDts(projectDir, manifest) {
82
85
  const dotLotics = path.join(projectDir, ".lotics");
83
86
  fs.mkdirSync(dotLotics, { recursive: true });
84
- const dts = generateAppWorkflowsDts(workflows);
85
- fs.writeFileSync(path.join(dotLotics, "app_workflows.d.ts"), dts);
87
+ fs.writeFileSync(path.join(dotLotics, "app_workflows.d.ts"), generateAppWorkflowsDts(manifest.workflows));
88
+ fs.writeFileSync(path.join(dotLotics, "app_queries.d.ts"), generateAppQueriesDts(manifest.queries));
86
89
  }
87
90
  /**
88
91
  * Stamp the post-extraction manifest with server-authoritative meta. Called
@@ -103,8 +106,9 @@ export function stampPulledManifest(projectDir, args) {
103
106
  current_version_id: args.current_version_id,
104
107
  version_number: args.version_number,
105
108
  workflows: args.workflows,
109
+ queries: args.queries,
106
110
  });
107
- writeAppWorkflowsDts(projectDir, args.workflows);
111
+ writeAppDts(projectDir, { workflows: args.workflows, queries: args.queries });
108
112
  }
109
113
  async function downloadToFile(url, destPath) {
110
114
  const response = await fetch(url);
@@ -155,7 +159,7 @@ export async function appCreate(client, args) {
155
159
  // Codegen pass for AppWorkflows typing. Empty manifest on first create —
156
160
  // emits a base augmentation file so future deploys produce idempotent diffs
157
161
  // rather than introducing a new tracked file later.
158
- writeAppWorkflowsDts(targetPath, {});
162
+ writeAppDts(targetPath, {});
159
163
  console.error("Installing npm dependencies...");
160
164
  await runNpm(["install"], targetPath);
161
165
  console.error("Building initial version...");
@@ -217,6 +221,7 @@ export async function appPull(client, args) {
217
221
  current_version_id: app.current_version_id,
218
222
  version_number: version.version,
219
223
  workflows: app.workflows ?? {},
224
+ queries: app.queries ?? {},
220
225
  });
221
226
  console.error(`Installing npm dependencies...`);
222
227
  await runNpm(["install"], targetPath);
@@ -239,7 +244,7 @@ export async function appDeploy(client, args) {
239
244
  // Regenerate AppWorkflows typing before the build picks up source. Keeps
240
245
  // .lotics/app_workflows.d.ts in sync with the manifest's workflows map
241
246
  // every time the developer ships.
242
- writeAppWorkflowsDts(projectDir, meta.workflows);
247
+ writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries });
243
248
  // Build locally so the server doesn't need a build sandbox in v1.
244
249
  console.error("Building...");
245
250
  await runNpm(["run", "build"], projectDir);
@@ -275,6 +280,9 @@ export async function appDeploy(client, args) {
275
280
  // Sync apps.workflows from the manifest. Server validates each
276
281
  // workflow_id exists in the workspace before committing.
277
282
  workflows: meta.workflows ?? {},
283
+ // Sync apps.queries from the manifest. Server validates each query
284
+ // template (parseQueryNode, table access, param coverage).
285
+ queries: meta.queries ?? {},
278
286
  // Opt into destructive workflow removal. Default false — the server
279
287
  // rejects deploys whose manifest is missing aliases the App row has.
280
288
  force_workflow_sync: args.forceWorkflowSync,
@@ -308,8 +316,8 @@ export async function appDeploy(client, args) {
308
316
  * `lotics app dev [path] [--port=5174] [--vite-port=5173]`
309
317
  *
310
318
  * Local dev mode for iframe apps. Spawns Vite + a postMessage RPC forwarder
311
- * that bridges the iframe's hooks (useQuery, useMutate, useAction,
312
- * useWorkflow) to api.lotics.ai using the CLI's stored API key. Wrapper
319
+ * that bridges the iframe's hooks (useQuery, useWorkflow) to api.lotics.ai
320
+ * using the CLI's stored API key. Wrapper
313
321
  * iframe matches the production sandbox attributes exactly — null origin,
314
322
  * allow-scripts — so prod-equivalent runtime behavior surfaces in dev.
315
323
  *
@@ -323,7 +331,7 @@ export async function appDev(client, args) {
323
331
  // Regenerate AppWorkflows typing before Vite spins up so the dev typecheck
324
332
  // sees the current shape. Doesn't watch for manifest changes mid-session —
325
333
  // re-running `lotics app dev` after editing the manifest is the loop.
326
- writeAppWorkflowsDts(projectDir, meta.workflows);
334
+ writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries });
327
335
  // Sanity: confirm the app exists in the workspace the CLI is auth'd into.
328
336
  // Surfaces a clear error if the project's app_id has been deleted or the
329
337
  // CLI is pointed at the wrong workspace.
@@ -56,6 +56,7 @@ describe("stampPulledManifest", () => {
56
56
  agent_authored: { workflow_id: "wfl_authored_by_agent" },
57
57
  renamed: { workflow_id: "wfl_renamed_on_server" },
58
58
  },
59
+ queries: {},
59
60
  });
60
61
  const stamped = readStampedManifest();
61
62
  expect(stamped.app_id).toBe("app_live");
@@ -80,6 +81,7 @@ describe("stampPulledManifest", () => {
80
81
  current_version_id: "apv_x",
81
82
  version_number: 2,
82
83
  workflows: {},
84
+ queries: {},
83
85
  });
84
86
  const stamped = readStampedManifest();
85
87
  expect(stamped.workflows).toEqual({});
@@ -94,6 +96,7 @@ describe("stampPulledManifest", () => {
94
96
  workflows: {
95
97
  only_live: { workflow_id: "wfl_only_live" },
96
98
  },
99
+ queries: {},
97
100
  });
98
101
  const dtsPath = path.join(workDir, ".lotics", "app_workflows.d.ts");
99
102
  expect(fs.existsSync(dtsPath)).toBe(true);
@@ -86,6 +86,14 @@ export declare class LoticsClient {
86
86
  workflow_id: string;
87
87
  inputs?: Record<string, unknown>;
88
88
  }> | null;
89
+ /**
90
+ * Live alias → query declaration map from `apps.queries`. Source of truth
91
+ * for `lotics app pull`. Null/undefined on apps with no declared queries.
92
+ */
93
+ queries?: Record<string, {
94
+ ast: unknown;
95
+ params?: Record<string, unknown>;
96
+ }> | null;
89
97
  }>;
90
98
  createApp(body: {
91
99
  name: string;
@@ -107,22 +115,15 @@ export declare class LoticsClient {
107
115
  }>;
108
116
  getAppVersionSourceUrl(app_id: string, version_id: string): Promise<string>;
109
117
  /**
110
- * Run a query AST scoped to an app's IAM principal.
111
- * Mirrors POST /v1/apps/{app_id}/query.
118
+ * Run a named query declared in the app's manifest, scoped to the app's IAM
119
+ * principal. Mirrors POST /v1/apps/{app_id}/query.
112
120
  */
113
- appQuery(app_id: string, ast: unknown): Promise<{
121
+ appQuery(app_id: string, body: {
122
+ alias: string;
123
+ params?: Record<string, unknown>;
124
+ }): Promise<{
114
125
  rows: unknown[];
115
126
  }>;
116
- /**
117
- * Update records in a workspace table scoped to an app's IAM principal.
118
- * Mirrors PATCH /v1/apps/{app_id}/tables/{table_id}/records.
119
- */
120
- appMutate(app_id: string, table_id: string, records: unknown): Promise<unknown>;
121
- /**
122
- * Execute an app-declared action.
123
- * Mirrors POST /v1/apps/{app_id}/actions/{action_id}.
124
- */
125
- appAction(app_id: string, action_id: string, inputs: unknown): Promise<unknown>;
126
127
  /**
127
128
  * Execute a workflow by alias declared in package.json#lotics.workflows.
128
129
  * Mirrors POST /v1/apps/{app_id}/workflows/{alias}/execute.
@@ -147,6 +148,16 @@ export declare class LoticsClient {
147
148
  workflow_id: string;
148
149
  inputs?: Record<string, unknown>;
149
150
  }>;
151
+ /**
152
+ * Alias → query declaration map from `package.json#lotics.queries`. Each
153
+ * value is `{ ast, params? }` — a fixed query AST template and an optional
154
+ * typed param schema. Always sent (empty object when none declared) so the
155
+ * server overwrites apps.queries authoritatively.
156
+ */
157
+ queries?: Record<string, {
158
+ ast: unknown;
159
+ params?: Record<string, unknown>;
160
+ }>;
150
161
  /**
151
162
  * Opt into destructive workflow removal. When false/absent, the server
152
163
  * rejects a deploy whose `workflows` map is missing aliases that exist
@@ -157,30 +157,16 @@ export class LoticsClient {
157
157
  return result.url;
158
158
  }
159
159
  // ── App iframe RPC endpoints ──────────────────────────────────────────────
160
- // These mirror the four ops handled by frontend/features/app_ui/app_iframe_host.tsx.
160
+ // These mirror the two ops handled by frontend/features/app_ui/app_iframe_host.tsx.
161
161
  // The deployed iframe sends postMessage to the parent frontend, which calls
162
162
  // these same endpoints via the user's session cookie. `lotics app dev`
163
163
  // forwards the iframe's postMessage to these methods using the CLI's API key.
164
164
  /**
165
- * Run a query AST scoped to an app's IAM principal.
166
- * Mirrors POST /v1/apps/{app_id}/query.
165
+ * Run a named query declared in the app's manifest, scoped to the app's IAM
166
+ * principal. Mirrors POST /v1/apps/{app_id}/query.
167
167
  */
168
- async appQuery(app_id, ast) {
169
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/query`, { ast });
170
- }
171
- /**
172
- * Update records in a workspace table scoped to an app's IAM principal.
173
- * Mirrors PATCH /v1/apps/{app_id}/tables/{table_id}/records.
174
- */
175
- async appMutate(app_id, table_id, records) {
176
- return this.request("PATCH", `/v1/apps/${encodeURIComponent(app_id)}/tables/${encodeURIComponent(table_id)}/records`, { records });
177
- }
178
- /**
179
- * Execute an app-declared action.
180
- * Mirrors POST /v1/apps/{app_id}/actions/{action_id}.
181
- */
182
- async appAction(app_id, action_id, inputs) {
183
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/actions/${encodeURIComponent(action_id)}`, { inputs });
168
+ async appQuery(app_id, body) {
169
+ return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/query`, body);
184
170
  }
185
171
  /**
186
172
  * Execute a workflow by alias declared in package.json#lotics.workflows.
@@ -205,6 +191,9 @@ export class LoticsClient {
205
191
  // Always send workflows — empty object is meaningful (clears any
206
192
  // previously-declared aliases). Server validates each entry.
207
193
  formData.append("workflows", JSON.stringify(args.workflows ?? {}));
194
+ // Always send queries — empty object clears any previously-declared
195
+ // named queries. Server validates each template.
196
+ formData.append("queries", JSON.stringify(args.queries ?? {}));
208
197
  // Only post the override flag when the user explicitly opts in. The
209
198
  // server defaults to "honor the guard" — opt-in is loud, opt-out
210
199
  // requires intent.
@@ -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" | "mutate" | "action" | "workflow";
9
+ export type RpcOp = "query" | "workflow";
10
10
  export interface RpcRequest {
11
11
  app_id: string;
12
12
  op: RpcOp;
@@ -5,7 +5,7 @@
5
5
  * Errors are thrown — the HTTP server caller serializes them to a 500 with
6
6
  * { message }. Same shape as the production iframe-host's error path.
7
7
  */
8
- const SUPPORTED_OPS = new Set(["query", "mutate", "action", "workflow"]);
8
+ const SUPPORTED_OPS = new Set(["query", "workflow"]);
9
9
  export async function dispatchRpc(client, body) {
10
10
  if (!body || typeof body.app_id !== "string" || typeof body.op !== "string") {
11
11
  throw new Error("RPC envelope must include app_id and op");
@@ -16,25 +16,10 @@ export async function dispatchRpc(client, body) {
16
16
  switch (body.op) {
17
17
  case "query": {
18
18
  const p = body.payload;
19
- const ast = p?.ast;
20
- if (ast === undefined) {
21
- throw new Error("query payload must include `ast`");
22
- }
23
- return client.appQuery(body.app_id, ast);
24
- }
25
- case "mutate": {
26
- const p = body.payload;
27
- if (!p || typeof p.table_id !== "string") {
28
- throw new Error("mutate payload must include `table_id`");
29
- }
30
- return client.appMutate(body.app_id, p.table_id, p.records);
31
- }
32
- case "action": {
33
- const p = body.payload;
34
- if (!p || typeof p.action_id !== "string") {
35
- throw new Error("action payload must include `action_id`");
19
+ if (!p || typeof p.alias !== "string") {
20
+ throw new Error("query payload must include `alias`");
36
21
  }
37
- return client.appAction(body.app_id, p.action_id, p.inputs);
22
+ return client.appQuery(body.app_id, { alias: p.alias, params: p.params });
38
23
  }
39
24
  case "workflow": {
40
25
  const p = body.payload;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Codegen: emit `.lotics/app_queries.d.ts` from the app's manifest.
3
+ *
4
+ * The starter tsconfig.json includes `.lotics/` in its `include` list, so this
5
+ * file is auto-discovered by `tsc --noEmit` and the bundler. The augmentation
6
+ * adds a typed entry to `AppQueries` for every alias declared in
7
+ * `package.json#lotics.queries`, mapping each to its declared param shape:
8
+ *
9
+ * - `params` declared → `alias: { …declared shape… }`
10
+ * - `params` omitted → `alias: Record<string, never>` (no params)
11
+ *
12
+ * Result: `useQuery("alias", params)` is typed against the manifest, and an
13
+ * undeclared alias is a compile-time error.
14
+ *
15
+ * The typed-input → TS-type mappers are shared with the workflow codegen —
16
+ * both surfaces use the same `AppWorkflowInput` vocabulary.
17
+ *
18
+ * Pure function. Same inputs → same output. Idempotent.
19
+ */
20
+ import type { AppQueryDeclaration } from "./app_commands.js";
21
+ export declare function generateAppQueriesDts(queries: Record<string, AppQueryDeclaration> | undefined): string;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Codegen: emit `.lotics/app_queries.d.ts` from the app's manifest.
3
+ *
4
+ * The starter tsconfig.json includes `.lotics/` in its `include` list, so this
5
+ * file is auto-discovered by `tsc --noEmit` and the bundler. The augmentation
6
+ * adds a typed entry to `AppQueries` for every alias declared in
7
+ * `package.json#lotics.queries`, mapping each to its declared param shape:
8
+ *
9
+ * - `params` declared → `alias: { …declared shape… }`
10
+ * - `params` omitted → `alias: Record<string, never>` (no params)
11
+ *
12
+ * Result: `useQuery("alias", params)` is typed against the manifest, and an
13
+ * undeclared alias is a compile-time error.
14
+ *
15
+ * The typed-input → TS-type mappers are shared with the workflow codegen —
16
+ * both surfaces use the same `AppWorkflowInput` vocabulary.
17
+ *
18
+ * Pure function. Same inputs → same output. Idempotent.
19
+ */
20
+ import { inputsToType, isValidIdentifier } from "./generate_app_workflows_dts.js";
21
+ const HEADER = `// Auto-generated by 'lotics app pull/dev/deploy'.
22
+ // DO NOT EDIT — regenerated from package.json#lotics.queries.
23
+ //
24
+ // This file gives \`useQuery("alias", params)\` typed params at call sites by
25
+ // augmenting the @lotics/app-sdk \`AppQueries\` interface.
26
+
27
+ import "@lotics/app-sdk";
28
+ `;
29
+ export function generateAppQueriesDts(queries) {
30
+ const entries = Object.entries(queries ?? {});
31
+ if (entries.length === 0) {
32
+ return `${HEADER}
33
+ // No queries declared in package.json#lotics.queries.
34
+ // Add an entry to enable typed useQuery("alias", params) at call sites.
35
+ declare module "@lotics/app-sdk" {
36
+ interface AppQueries {}
37
+ }
38
+ `;
39
+ }
40
+ // Sort alphabetically for deterministic output across regenerations.
41
+ entries.sort(([a], [b]) => a.localeCompare(b));
42
+ const lines = [];
43
+ for (const [alias, declaration] of entries) {
44
+ // `inputsToType` returns `Record<string, never>` for an empty/absent
45
+ // param schema — exactly the no-params placeholder `useQuery` expects.
46
+ const valueType = inputsToType(declaration.params ?? {});
47
+ const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
48
+ lines.push(` ${aliasKey}: ${valueType};`);
49
+ }
50
+ return `${HEADER}
51
+ declare module "@lotics/app-sdk" {
52
+ interface AppQueries {
53
+ ${lines.join("\n")}
54
+ }
55
+ }
56
+ `;
57
+ }
@@ -15,3 +15,16 @@
15
15
  */
16
16
  import type { AppWorkflowDeclaration } from "./app_commands.js";
17
17
  export declare function generateAppWorkflowsDts(workflows: Record<string, AppWorkflowDeclaration> | undefined): string;
18
+ /**
19
+ * Map an `inputs: { key: AppWorkflowInput }` schema to a TS literal type
20
+ * string. Mirrors the backend .d.ts generator's behavior — same vocabulary,
21
+ * same conventions — so workflow body types and app call-site types agree.
22
+ *
23
+ * Note: `inputs` here is `Record<string, unknown>` rather than typed because
24
+ * the CLI doesn't import zod or the apps schema (zero-dep policy in @lotics/sdk).
25
+ * We pattern-match on the `type` field and let unknown shapes fall through
26
+ * to `unknown` rather than throwing — the server's deploy-time schema parse
27
+ * catches structurally-invalid declarations before they reach us.
28
+ */
29
+ export declare function inputsToType(inputs: Record<string, unknown>): string;
30
+ export declare function isValidIdentifier(name: string): boolean;
@@ -64,7 +64,7 @@ ${lines.join("\n")}
64
64
  * to `unknown` rather than throwing — the server's deploy-time schema parse
65
65
  * catches structurally-invalid declarations before they reach us.
66
66
  */
67
- function inputsToType(inputs) {
67
+ export function inputsToType(inputs) {
68
68
  const fields = [];
69
69
  for (const [key, decl] of Object.entries(inputs)) {
70
70
  if (decl === null || typeof decl !== "object")
@@ -118,6 +118,6 @@ function inputDeclToTsType(decl) {
118
118
  }
119
119
  }
120
120
  const IDENTIFIER_REGEX = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
121
- function isValidIdentifier(name) {
121
+ export function isValidIdentifier(name) {
122
122
  return IDENTIFIER_REGEX.test(name);
123
123
  }
@@ -47,7 +47,7 @@ export function buildStarterTemplate(args) {
47
47
  test: "vitest run",
48
48
  },
49
49
  dependencies: {
50
- "@lotics/app-sdk": "^0.2.0",
50
+ "@lotics/app-sdk": "^0.5.0",
51
51
  "@lotics/ui": "^0.2.0",
52
52
  "@react-native-picker/picker": "^2.7.0",
53
53
  "expo-image": "~3.0.9",
@@ -239,7 +239,7 @@ export default function App() {
239
239
  </View>
240
240
  </Card>
241
241
  <Text color="muted" size="sm">
242
- Hooks from @lotics/app-sdk: useTable, useMutate, useAction, useQuery, useWorkflow.
242
+ Hooks from @lotics/app-sdk: useQuery, useWorkflow.
243
243
  </Text>
244
244
  <Button title="Get started" onPress={() => {}} color="primary" />
245
245
  </View>
@@ -400,7 +400,7 @@ lotics app deploy -m "what" # with a commit-message-style note
400
400
  ## SDK + UI components
401
401
 
402
402
  \`\`\`tsx
403
- import { mount, useTable, useMutate, useAction, useQuery, useWorkflow } from "@lotics/app-sdk";
403
+ import { mount, useQuery, useWorkflow } from "@lotics/app-sdk";
404
404
  import { Stack } from "@lotics/ui/stack";
405
405
  import { Card } from "@lotics/ui/card";
406
406
  import { Button } from "@lotics/ui/button";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {