@lotics/cli 0.49.0 → 0.51.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.
@@ -26,6 +26,22 @@ export type AppQueryDeclaration = {
26
26
  ast: unknown;
27
27
  params?: Record<string, unknown>;
28
28
  };
29
+ /**
30
+ * Manifest declaration for one streaming agent:
31
+ * `"alias": { instructions, tool_names, model_id, inputs?, outputs? }`
32
+ *
33
+ * A read-only reflection of the live App row (`apps.agents`, owned by
34
+ * `set_app_agent`), refreshed by `lotics app pull` and used only to codegen
35
+ * `useAgentRun` typings. `inputs`/`outputs` drive the typed call site; the rest
36
+ * is carried for fidelity.
37
+ */
38
+ export type AppAgentDeclaration = {
39
+ instructions?: string;
40
+ tool_names?: string[];
41
+ model_id?: string;
42
+ inputs?: Record<string, unknown>;
43
+ outputs?: Record<string, unknown>;
44
+ };
29
45
  /**
30
46
  * Stamp the post-extraction manifest with server-authoritative meta. Called
31
47
  * by `appPull` after the source archive lands on disk.
@@ -45,6 +61,7 @@ export declare function stampPulledManifest(projectDir: string, args: {
45
61
  version_number: number;
46
62
  workflows: Record<string, AppWorkflowDeclaration>;
47
63
  queries: Record<string, AppQueryDeclaration>;
64
+ agents: Record<string, AppAgentDeclaration>;
48
65
  }): void;
49
66
  /**
50
67
  * `lotics app create <name> [path]`
@@ -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 { generateAppAgentsDts } from "./generate_app_agents_dts.js";
21
22
  import { generateAppQueriesDts } from "./generate_app_queries_dts.js";
22
23
  /**
23
24
  * Resolve the latest published version of a package from the npm registry.
@@ -92,6 +93,7 @@ function readAppMeta(projectDir) {
92
93
  version_number: pkg.lotics.version_number ?? null,
93
94
  workflows: pkg.lotics.workflows ?? {},
94
95
  queries: pkg.lotics.queries ?? {},
96
+ agents: pkg.lotics.agents ?? {},
95
97
  capabilities: pkg.lotics.capabilities,
96
98
  };
97
99
  }
@@ -113,6 +115,7 @@ function writeAppDts(projectDir, manifest) {
113
115
  fs.mkdirSync(dotLotics, { recursive: true });
114
116
  fs.writeFileSync(path.join(dotLotics, "app_workflows.d.ts"), generateAppWorkflowsDts(manifest.workflows));
115
117
  fs.writeFileSync(path.join(dotLotics, "app_queries.d.ts"), generateAppQueriesDts(manifest.queries));
118
+ fs.writeFileSync(path.join(dotLotics, "app_agents.d.ts"), generateAppAgentsDts(manifest.agents));
116
119
  }
117
120
  /**
118
121
  * Stamp the post-extraction manifest with server-authoritative meta. Called
@@ -134,8 +137,9 @@ export function stampPulledManifest(projectDir, args) {
134
137
  version_number: args.version_number,
135
138
  workflows: args.workflows,
136
139
  queries: args.queries,
140
+ agents: args.agents,
137
141
  });
138
- writeAppDts(projectDir, { workflows: args.workflows, queries: args.queries });
142
+ writeAppDts(projectDir, { workflows: args.workflows, queries: args.queries, agents: args.agents });
139
143
  }
140
144
  async function downloadToFile(url, destPath) {
141
145
  const response = await fetch(url);
@@ -281,6 +285,7 @@ export async function appPull(client, args) {
281
285
  version_number: version.version,
282
286
  workflows: app.workflows ?? {},
283
287
  queries: app.queries ?? {},
288
+ agents: app.agents ?? {},
284
289
  });
285
290
  console.error(`Installing npm dependencies...`);
286
291
  await runNpm(["install"], targetPath);
@@ -355,7 +360,7 @@ export async function appDeploy(client, args) {
355
360
  // Regenerate AppWorkflows typing before the build picks up source. Keeps
356
361
  // .lotics/app_workflows.d.ts in sync with the manifest's workflows map
357
362
  // every time the developer ships.
358
- writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries });
363
+ writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries, agents: meta.agents });
359
364
  // Build locally so the server doesn't need a build sandbox in v1.
360
365
  console.error("Building...");
361
366
  await runNpm(["run", "build"], projectDir);
@@ -469,7 +474,7 @@ export async function appDev(client, args) {
469
474
  // Regenerate AppWorkflows typing before Vite spins up so the dev typecheck
470
475
  // sees the current shape. Doesn't watch for manifest changes mid-session —
471
476
  // re-running `lotics app dev` after editing the manifest is the loop.
472
- writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries });
477
+ writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries, agents: meta.agents });
473
478
  // Sanity: confirm the app exists in the workspace the CLI is auth'd into.
474
479
  // Surfaces a clear error if the project's app_id has been deleted or the
475
480
  // CLI is pointed at the wrong workspace.
@@ -57,6 +57,7 @@ describe("stampPulledManifest", () => {
57
57
  renamed: { workflow_id: "wfl_renamed_on_server" },
58
58
  },
59
59
  queries: {},
60
+ agents: {},
60
61
  });
61
62
  const stamped = readStampedManifest();
62
63
  expect(stamped.app_id).toBe("app_live");
@@ -82,6 +83,7 @@ describe("stampPulledManifest", () => {
82
83
  version_number: 2,
83
84
  workflows: {},
84
85
  queries: {},
86
+ agents: {},
85
87
  });
86
88
  const stamped = readStampedManifest();
87
89
  expect(stamped.workflows).toEqual({});
@@ -97,6 +99,7 @@ describe("stampPulledManifest", () => {
97
99
  only_live: { workflow_id: "wfl_only_live" },
98
100
  },
99
101
  queries: {},
102
+ agents: {},
100
103
  });
101
104
  const dtsPath = path.join(workDir, ".lotics", "app_workflows.d.ts");
102
105
  expect(fs.existsSync(dtsPath)).toBe(true);
package/dist/client.d.ts CHANGED
@@ -130,6 +130,19 @@ export declare class LoticsClient {
130
130
  ast: unknown;
131
131
  params?: Record<string, unknown>;
132
132
  }> | null;
133
+ /**
134
+ * Live alias → agent declaration map from `apps.agents`. Source of truth for
135
+ * `lotics app pull` — supersedes the source archive so `set_app_agent`
136
+ * authoring survives the pull. Null/undefined on apps with no declared
137
+ * agents. Drives `useAgentRun` typing.
138
+ */
139
+ agents?: Record<string, {
140
+ instructions?: string;
141
+ tool_names?: string[];
142
+ model_id?: string;
143
+ inputs?: Record<string, unknown>;
144
+ outputs?: Record<string, unknown>;
145
+ }> | null;
133
146
  }>;
134
147
  createApp(body: {
135
148
  name: string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Codegen: emit `.lotics/app_agents.d.ts` from the app's manifest.
3
+ *
4
+ * Mirrors `generate_app_workflows_dts` — agents reuse the exact same typed
5
+ * input/output vocabulary, so the `inputsToType` / `objectFieldsToType` mappers
6
+ * are shared. The augmentation adds a typed entry to `AppAgents` for every alias
7
+ * declared in `package.json#lotics.agents`:
8
+ *
9
+ * - No declared `inputs` → `alias: Record<string, unknown>` (untyped payload)
10
+ * - Declared `inputs` → `alias: { …declared shape… }`
11
+ * - Declared `outputs` → an `AppAgentResults[alias]` entry → typed `output`
12
+ *
13
+ * Pure function. Same inputs → same output. Idempotent.
14
+ */
15
+ import type { AppAgentDeclaration } from "./app_commands.js";
16
+ export declare function generateAppAgentsDts(agents: Record<string, AppAgentDeclaration> | undefined): string;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Codegen: emit `.lotics/app_agents.d.ts` from the app's manifest.
3
+ *
4
+ * Mirrors `generate_app_workflows_dts` — agents reuse the exact same typed
5
+ * input/output vocabulary, so the `inputsToType` / `objectFieldsToType` mappers
6
+ * are shared. The augmentation adds a typed entry to `AppAgents` for every alias
7
+ * declared in `package.json#lotics.agents`:
8
+ *
9
+ * - No declared `inputs` → `alias: Record<string, unknown>` (untyped payload)
10
+ * - Declared `inputs` → `alias: { …declared shape… }`
11
+ * - Declared `outputs` → an `AppAgentResults[alias]` entry → typed `output`
12
+ *
13
+ * Pure function. Same inputs → same output. Idempotent.
14
+ */
15
+ import { inputsToType, objectFieldsToType, isValidIdentifier } from "./generate_app_workflows_dts.js";
16
+ const HEADER = `// Auto-generated by 'lotics app pull/dev/deploy'.
17
+ // DO NOT EDIT — regenerated from package.json#lotics.agents.
18
+ //
19
+ // This file gives \`useAgentRun("alias")\` typed input + output at call sites by
20
+ // augmenting the @lotics/app-sdk \`AppAgents\` / \`AppAgentResults\` interfaces.
21
+
22
+ import "@lotics/app-sdk";
23
+ `;
24
+ export function generateAppAgentsDts(agents) {
25
+ const entries = Object.entries(agents ?? {});
26
+ if (entries.length === 0) {
27
+ return `${HEADER}
28
+ // No agents declared in package.json#lotics.agents.
29
+ // Add an entry to enable typed useAgentRun<"alias"> at call sites.
30
+ declare module "@lotics/app-sdk" {
31
+ interface AppAgents {}
32
+ }
33
+ `;
34
+ }
35
+ // Sort alphabetically for deterministic output across regenerations.
36
+ entries.sort(([a], [b]) => a.localeCompare(b));
37
+ const inputLines = [];
38
+ const resultLines = [];
39
+ for (const [alias, declaration] of entries) {
40
+ const valueType = declaration.inputs
41
+ ? inputsToType(declaration.inputs)
42
+ : "Record<string, unknown>";
43
+ const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
44
+ inputLines.push(` ${aliasKey}: ${valueType};`);
45
+ if (declaration.outputs) {
46
+ resultLines.push(` ${aliasKey}: ${objectFieldsToType(declaration.outputs)};`);
47
+ }
48
+ }
49
+ const resultsBlock = resultLines.length > 0
50
+ ? `
51
+ interface AppAgentResults {
52
+ ${resultLines.join("\n")}
53
+ }`
54
+ : "";
55
+ return `${HEADER}
56
+ declare module "@lotics/app-sdk" {
57
+ interface AppAgents {
58
+ ${inputLines.join("\n")}
59
+ }${resultsBlock}
60
+ }
61
+ `;
62
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { generateAppAgentsDts } from "./generate_app_agents_dts.js";
3
+ describe("generateAppAgentsDts", () => {
4
+ it("emits an empty AppAgents interface when no agents are declared", () => {
5
+ const out = generateAppAgentsDts(undefined);
6
+ expect(out).toContain("interface AppAgents {}");
7
+ expect(out).not.toContain("interface AppAgentResults");
8
+ });
9
+ it("types the input payload from declared inputs", () => {
10
+ const out = generateAppAgentsDts({
11
+ recognize: { inputs: { photo: { type: "file" }, prompt: { type: "text", required: false } } },
12
+ });
13
+ expect(out).toContain("recognize: {");
14
+ expect(out).toContain("photo: string;");
15
+ expect(out).toContain("prompt?: string;");
16
+ });
17
+ it("leaves an inputs-less agent as an untyped payload", () => {
18
+ const out = generateAppAgentsDts({ summarize: {} });
19
+ expect(out).toContain("summarize: Record<string, unknown>;");
20
+ });
21
+ it("emits AppAgentResults from declared outputs, including nested objects", () => {
22
+ const out = generateAppAgentsDts({
23
+ recognize: {
24
+ inputs: { photo: { type: "file" } },
25
+ outputs: {
26
+ style: { type: "text" },
27
+ dimensions: {
28
+ type: "object",
29
+ fields: { width_mm: { type: "number" }, height_mm: { type: "number" } },
30
+ },
31
+ confidence: { type: "number" },
32
+ },
33
+ },
34
+ });
35
+ expect(out).toContain("interface AppAgentResults {");
36
+ expect(out).toContain("style: string");
37
+ expect(out).toContain("width_mm: number");
38
+ expect(out).toContain("confidence: number");
39
+ });
40
+ it("is deterministic and sorts aliases", () => {
41
+ const a = generateAppAgentsDts({ zebra: {}, alpha: {} });
42
+ expect(a.indexOf("alpha:")).toBeLessThan(a.indexOf("zebra:"));
43
+ expect(generateAppAgentsDts({ zebra: {}, alpha: {} })).toBe(a);
44
+ });
45
+ });
package/dist/src/cli.js CHANGED
@@ -30434,7 +30434,23 @@ export default defineConfig({
30434
30434
  optimizer: {
30435
30435
  web: {
30436
30436
  enabled: true,
30437
- include: ["react-native", "react-native-web", "react-native-svg", "@react-native-picker/picker"],
30437
+ // react + react-dom + the @testing-library renderer must ride in the
30438
+ // SAME optimized chunk as react-native-web, so a rendered tree and the
30439
+ // renderer share ONE react instance. Without this, a test that renders a
30440
+ // non-trivial App tree hits a null hooks dispatcher ("Cannot read
30441
+ // properties of null (reading 'useState')") the moment it mounts.
30442
+ include: [
30443
+ "react",
30444
+ "react-dom",
30445
+ "react-dom/client",
30446
+ "react-dom/test-utils",
30447
+ "react-native",
30448
+ "react-native-web",
30449
+ "react-native-svg",
30450
+ "@react-native-picker/picker",
30451
+ "@testing-library/react",
30452
+ "@testing-library/dom",
30453
+ ],
30438
30454
  esbuildOptions: {
30439
30455
  resolveExtensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js", ".json"],
30440
30456
  },
@@ -31358,8 +31374,52 @@ function isValidIdentifier(name) {
31358
31374
  return IDENTIFIER_REGEX.test(name);
31359
31375
  }
31360
31376
 
31361
- // src/generate_app_queries_dts.ts
31377
+ // src/generate_app_agents_dts.ts
31362
31378
  var HEADER2 = `// Auto-generated by 'lotics app pull/dev/deploy'.
31379
+ // DO NOT EDIT \u2014 regenerated from package.json#lotics.agents.
31380
+ //
31381
+ // This file gives \`useAgentRun("alias")\` typed input + output at call sites by
31382
+ // augmenting the @lotics/app-sdk \`AppAgents\` / \`AppAgentResults\` interfaces.
31383
+
31384
+ import "@lotics/app-sdk";
31385
+ `;
31386
+ function generateAppAgentsDts(agents) {
31387
+ const entries = Object.entries(agents ?? {});
31388
+ if (entries.length === 0) {
31389
+ return `${HEADER2}
31390
+ // No agents declared in package.json#lotics.agents.
31391
+ // Add an entry to enable typed useAgentRun<"alias"> at call sites.
31392
+ declare module "@lotics/app-sdk" {
31393
+ interface AppAgents {}
31394
+ }
31395
+ `;
31396
+ }
31397
+ entries.sort(([a], [b]) => a.localeCompare(b));
31398
+ const inputLines = [];
31399
+ const resultLines = [];
31400
+ for (const [alias, declaration] of entries) {
31401
+ const valueType = declaration.inputs ? inputsToType(declaration.inputs) : "Record<string, unknown>";
31402
+ const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
31403
+ inputLines.push(` ${aliasKey}: ${valueType};`);
31404
+ if (declaration.outputs) {
31405
+ resultLines.push(` ${aliasKey}: ${objectFieldsToType(declaration.outputs)};`);
31406
+ }
31407
+ }
31408
+ const resultsBlock = resultLines.length > 0 ? `
31409
+ interface AppAgentResults {
31410
+ ${resultLines.join("\n")}
31411
+ }` : "";
31412
+ return `${HEADER2}
31413
+ declare module "@lotics/app-sdk" {
31414
+ interface AppAgents {
31415
+ ${inputLines.join("\n")}
31416
+ }${resultsBlock}
31417
+ }
31418
+ `;
31419
+ }
31420
+
31421
+ // src/generate_app_queries_dts.ts
31422
+ var HEADER3 = `// Auto-generated by 'lotics app pull/dev/deploy'.
31363
31423
  // DO NOT EDIT \u2014 regenerated from package.json#lotics.queries.
31364
31424
  //
31365
31425
  // This file gives \`useQuery("alias", params)\` typed params at call sites by
@@ -31370,7 +31430,7 @@ import "@lotics/app-sdk";
31370
31430
  function generateAppQueriesDts(queries) {
31371
31431
  const entries = Object.entries(queries ?? {});
31372
31432
  if (entries.length === 0) {
31373
- return `${HEADER2}
31433
+ return `${HEADER3}
31374
31434
  // No queries declared in package.json#lotics.queries.
31375
31435
  // Add an entry to enable typed useQuery("alias", params) at call sites.
31376
31436
  declare module "@lotics/app-sdk" {
@@ -31385,7 +31445,7 @@ declare module "@lotics/app-sdk" {
31385
31445
  const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
31386
31446
  lines.push(` ${aliasKey}: ${valueType};`);
31387
31447
  }
31388
- return `${HEADER2}
31448
+ return `${HEADER3}
31389
31449
  declare module "@lotics/app-sdk" {
31390
31450
  interface AppQueries {
31391
31451
  ${lines.join("\n")}
@@ -31453,6 +31513,7 @@ function readAppMeta(projectDir) {
31453
31513
  version_number: pkg2.lotics.version_number ?? null,
31454
31514
  workflows: pkg2.lotics.workflows ?? {},
31455
31515
  queries: pkg2.lotics.queries ?? {},
31516
+ agents: pkg2.lotics.agents ?? {},
31456
31517
  capabilities: pkg2.lotics.capabilities
31457
31518
  };
31458
31519
  }
@@ -31473,6 +31534,10 @@ function writeAppDts(projectDir, manifest) {
31473
31534
  path4.join(dotLotics, "app_queries.d.ts"),
31474
31535
  generateAppQueriesDts(manifest.queries)
31475
31536
  );
31537
+ fs3.writeFileSync(
31538
+ path4.join(dotLotics, "app_agents.d.ts"),
31539
+ generateAppAgentsDts(manifest.agents)
31540
+ );
31476
31541
  }
31477
31542
  function stampPulledManifest(projectDir, args) {
31478
31543
  writeAppMeta(projectDir, {
@@ -31481,9 +31546,10 @@ function stampPulledManifest(projectDir, args) {
31481
31546
  current_version_id: args.current_version_id,
31482
31547
  version_number: args.version_number,
31483
31548
  workflows: args.workflows,
31484
- queries: args.queries
31549
+ queries: args.queries,
31550
+ agents: args.agents
31485
31551
  });
31486
- writeAppDts(projectDir, { workflows: args.workflows, queries: args.queries });
31552
+ writeAppDts(projectDir, { workflows: args.workflows, queries: args.queries, agents: args.agents });
31487
31553
  }
31488
31554
  async function downloadToFile(url, destPath) {
31489
31555
  const response = await fetch(url);
@@ -31572,7 +31638,8 @@ async function appPull(client, args) {
31572
31638
  current_version_id: app.current_version_id,
31573
31639
  version_number: version.version,
31574
31640
  workflows: app.workflows ?? {},
31575
- queries: app.queries ?? {}
31641
+ queries: app.queries ?? {},
31642
+ agents: app.agents ?? {}
31576
31643
  });
31577
31644
  console.error(`Installing npm dependencies...`);
31578
31645
  await runNpm(["install"], targetPath);
@@ -31619,7 +31686,7 @@ async function appDeploy(client, args) {
31619
31686
  Add to package.json#lotics.capabilities: ${block}`
31620
31687
  );
31621
31688
  }
31622
- writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries });
31689
+ writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries, agents: meta.agents });
31623
31690
  console.error("Building...");
31624
31691
  await runNpm(["run", "build"], projectDir);
31625
31692
  const distDir = path4.join(projectDir, "dist");
@@ -31710,7 +31777,7 @@ async function warnIfUnbranded(client, appId) {
31710
31777
  async function appDev(client, args) {
31711
31778
  const projectDir = path4.resolve(args.projectDir ?? process.cwd());
31712
31779
  const meta = readAppMeta(projectDir);
31713
- writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries });
31780
+ writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries, agents: meta.agents });
31714
31781
  const app = await client.getApp(meta.app_id);
31715
31782
  const handle = await startDevServer({
31716
31783
  projectDir,
@@ -253,7 +253,23 @@ export default defineConfig({
253
253
  optimizer: {
254
254
  web: {
255
255
  enabled: true,
256
- include: ["react-native", "react-native-web", "react-native-svg", "@react-native-picker/picker"],
256
+ // react + react-dom + the @testing-library renderer must ride in the
257
+ // SAME optimized chunk as react-native-web, so a rendered tree and the
258
+ // renderer share ONE react instance. Without this, a test that renders a
259
+ // non-trivial App tree hits a null hooks dispatcher ("Cannot read
260
+ // properties of null (reading 'useState')") the moment it mounts.
261
+ include: [
262
+ "react",
263
+ "react-dom",
264
+ "react-dom/client",
265
+ "react-dom/test-utils",
266
+ "react-native",
267
+ "react-native-web",
268
+ "react-native-svg",
269
+ "@react-native-picker/picker",
270
+ "@testing-library/react",
271
+ "@testing-library/dom",
272
+ ],
257
273
  esbuildOptions: {
258
274
  resolveExtensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js", ".json"],
259
275
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.49.0",
3
+ "version": "0.51.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {