lua-cli 3.52.0 → 3.54.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.
@@ -743,6 +743,29 @@ if (invokedBy && !invokedBy.supervisor) {
743
743
 
744
744
  A turn created by **another** agent accepts only `messages`/`prompt`, `threadId`, `runtimeContext`, `webhookPayload` and the reasoning `effort`.
745
745
 
746
+ **`Lua.identity.assertion({ audience })` — prove the call comes from this agent.** It returns
747
+ `{ token, expiresAt }`: a 60-second, single-use token that Lua signs. The token proves that the call comes from a
748
+ live execution of this agent. Send `token` as the `X-Lua-Agent-Assertion` header on one call to the system named by
749
+ `audience`. Only platform-allowlisted audiences are minted; today that is `'lua-api:staff'`, for an agent enrolled
750
+ as a staff service principal bound to that agent. Mint a fresh token for every call and never reuse one. Older
751
+ runtimes do not have it, so read it defensively:
752
+
753
+ ```typescript
754
+ const assertion = await Lua.identity?.assertion({ audience: 'lua-api:staff' });
755
+ if (!assertion) throw new Error('This runtime cannot prove the agent identity; upgrade lua-cli');
756
+ await fetch(url, { headers: { 'X-Lua-Agent-Assertion': assertion.token /* , ... */ } });
757
+ ```
758
+
759
+ It throws an `AgentIdentityError`. Check `err.code`:
760
+
761
+ - `agent_identity_requires_live_execution`: thrown outside a live execution, and always in `lua test` /
762
+ `lua dev`.
763
+ - `agent_identity_audience_not_allowed`: thrown for any audience that is not allowlisted.
764
+ - `agent_identity_input_invalid`: thrown when `audience` is missing.
765
+ - `agent_identity_unavailable`: the platform could not mint a token right now.
766
+
767
+ Inside a test session it throws a `TestSessionError` with `test_session_tier_unsupported` instead.
768
+
746
769
  - The platform drops every other field: the persona (`systemPrompt`), `model`, tool scoping, version preview, workflow role and billing identities.
747
770
  - It also resets a platform channel (`workflow`/`task`/`trigger`) to `agent-invocation`.
748
771
 
@@ -410,6 +410,19 @@ $ lua env delete OLD_KEY
410
410
  ✅ Deleted OLD_KEY
411
411
  ```
412
412
 
413
+ **Setting a secret without putting it on the command line (`--stdin`):**
414
+
415
+ ```bash
416
+ printf %s "$STRIPE_SECRET_KEY" | lua env production -k STRIPE_SECRET_KEY --stdin
417
+ lua env production -k STRIPE_SECRET_KEY --stdin < stripe-key.txt
418
+ ```
419
+
420
+ `-k KEY --stdin` reads the value from stdin instead of `-v`, so it never appears in shell history, `ps` output or an
421
+ agent transcript. Exactly one trailing newline (`\n` or `\r\n`) is dropped; everything else is kept. An empty value is
422
+ refused, and so is a terminal stdin (there is no hidden prompt; pipe or redirect the value). `--stdin` cannot be
423
+ combined with `-v`, `--list` or `--delete`. In `sandbox` a value with an inner line break is refused (the `.env` file
424
+ holds one `KEY=value` per line). The value is at most 64 KiB and is never printed.
425
+
413
426
  ---
414
427
 
415
428
  ### `lua persona`
@@ -766,6 +779,7 @@ With no arguments, prompts for a domain — **Skills** or **Agent templates**
766
779
 
767
780
  - `create`, `publish`, `view`, `versions` — manage a versioned snapshot of an agent's deployable manifest
768
781
  - `install`, `apply`, `status`, `installed`, `uninstall` — install onto one agent, or fleet-apply across many
782
+ - `publish --views a,b` — first-party templates only: ship these views of the source agent (web-app names, in order; the first opens first) with the version. Each is frozen from its deployed version and added to the agent once, when someone hires it. Without `--views` the version ships no views.
769
783
 
770
784
  **Example:**
771
785
 
package/docs/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # lua-cli v3.52.0
1
+ # lua-cli v3.54.0
2
2
 
3
- Welcome to the comprehensive API documentation for lua-cli v3.52.0. This guide covers every API, class, and function exported by the package.
3
+ Welcome to the comprehensive API documentation for lua-cli v3.54.0. This guide covers every API, class, and function exported by the package.
4
4
 
5
5
  ## 📚 Documentation Index
6
6
 
@@ -263,7 +263,7 @@ Each API doc includes:
263
263
 
264
264
  ---
265
265
 
266
- **Version:** 3.52.0
266
+ **Version:** 3.54.0
267
267
  **Last Updated:** October 2026
268
268
 
269
269
  ---
@@ -11,6 +11,7 @@ import { LuaTool } from 'lua-cli/skill';
11
11
  ## Overview
12
12
 
13
13
  Tools are the building blocks of your agent's capabilities. Each tool:
14
+
14
15
  - Has a unique name
15
16
  - Describes what it does
16
17
  - Validates its inputs with Zod schemas
@@ -21,30 +22,37 @@ Tools are the building blocks of your agent's capabilities. Each tool:
21
22
 
22
23
  ```typescript
23
24
  interface LuaTool<TInput extends ZodType = ZodType> {
24
- /**
25
+ /**
25
26
  * Unique tool name (lowercase, underscores only)
26
27
  * Example: 'get_weather', 'create_order'
27
28
  */
28
29
  name: string;
29
-
30
+
30
31
  /**
31
32
  * Human-readable description of what the tool does
32
33
  * The AI uses this to understand when to call the tool
33
34
  */
34
35
  description: string;
35
-
36
+
37
+ /**
38
+ * Optional text the chat shows while the tool runs and when it is done,
39
+ * instead of the tool's name. One text, or one for each moment.
40
+ * {argName} is replaced with that argument's value from the call.
41
+ */
42
+ label?: string | { running: string; done: string };
43
+
36
44
  /**
37
45
  * Zod schema defining the input structure
38
46
  * Validates and type-checks all inputs
39
47
  */
40
48
  inputSchema: TInput;
41
-
49
+
42
50
  /**
43
51
  * Optional Zod schema defining the output structure
44
52
  * Used for documentation and validation
45
53
  */
46
54
  outputSchema?: ZodType;
47
-
55
+
48
56
  /**
49
57
  * The function that executes when the tool is called
50
58
  * @param input - Validated input matching inputSchema
@@ -65,19 +73,19 @@ import { z } from 'zod';
65
73
  export default class GreetingTool implements LuaTool {
66
74
  name = 'greet_user';
67
75
  description = 'Generate a personalized greeting';
68
-
76
+
69
77
  inputSchema = z.object({
70
78
  name: z.string().describe('User name'),
71
- language: z.enum(['en', 'es', 'fr']).optional().describe('Language code')
79
+ language: z.enum(['en', 'es', 'fr']).optional().describe('Language code'),
72
80
  });
73
-
81
+
74
82
  async execute(input: z.infer<typeof this.inputSchema>) {
75
83
  const greetings = {
76
84
  en: `Hello, ${input.name}!`,
77
85
  es: `¡Hola, ${input.name}!`,
78
- fr: `Bonjour, ${input.name}!`
86
+ fr: `Bonjour, ${input.name}!`,
79
87
  };
80
-
88
+
81
89
  const lang = input.language || 'en';
82
90
  return { greeting: greetings[lang] };
83
91
  }
@@ -93,21 +101,21 @@ import { z } from 'zod';
93
101
  export default class CalculatorTool implements LuaTool {
94
102
  name = 'calculator';
95
103
  description = 'Perform mathematical calculations';
96
-
104
+
97
105
  inputSchema = z.object({
98
106
  operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
99
107
  a: z.number(),
100
- b: z.number()
108
+ b: z.number(),
101
109
  });
102
-
110
+
103
111
  outputSchema = z.object({
104
112
  result: z.number(),
105
- operation: z.string()
113
+ operation: z.string(),
106
114
  });
107
-
115
+
108
116
  async execute(input: z.infer<typeof this.inputSchema>) {
109
117
  let result: number;
110
-
118
+
111
119
  switch (input.operation) {
112
120
  case 'add':
113
121
  result = input.a + input.b;
@@ -125,15 +133,45 @@ export default class CalculatorTool implements LuaTool {
125
133
  result = input.a / input.b;
126
134
  break;
127
135
  }
128
-
136
+
129
137
  return {
130
138
  result,
131
- operation: input.operation
139
+ operation: input.operation,
132
140
  };
133
141
  }
134
142
  }
135
143
  ```
136
144
 
145
+ ### With a Label
146
+
147
+ While a tool runs, the chat shows the user what is happening. By default it shows the tool's name. Set `label` to show a sentence instead:
148
+
149
+ ```typescript
150
+ import { LuaTool } from 'lua-cli';
151
+ import { z } from 'zod';
152
+
153
+ export class CreateTicketTool implements LuaTool {
154
+ name = 'create_ticket';
155
+ description = "Create a Zendesk ticket when the knowledge base can't help";
156
+ label = { running: 'Creating a ticket for {subject}', done: 'Created a ticket for {subject}' };
157
+
158
+ inputSchema = z.object({ subject: z.string() });
159
+
160
+ async execute(input: z.infer<typeof this.inputSchema>) {
161
+ // ...
162
+ }
163
+ }
164
+ ```
165
+
166
+ The chat shows "Creating a ticket for "Refund request"…" while the tool runs and "Created a ticket for "Refund request"" when it is done.
167
+
168
+ - `label` is either one text for both moments (`label = 'Checked stock'`) or `{ running, done }`.
169
+ - `{argName}` is replaced with the value of that argument from the call. Only string and number arguments are filled in.
170
+ - Don't end the text with "…": the chat adds it while the tool runs.
171
+ - Each text must be a string literal in the code (a const holding one works too, `${...}` template values do not) of at most 80 characters. `lua compile` reports an empty or too-long label as an error, and ignores a computed label with a warning.
172
+
173
+ The same field works with `defineTool({ name, description, label, inputSchema, execute })` and with a plain tool object.
174
+
137
175
  ## Using Runtime APIs
138
176
 
139
177
  Tools can access Lua platform APIs:
@@ -145,30 +183,30 @@ import { z } from 'zod';
145
183
  export default class UserPreferencesTool implements LuaTool {
146
184
  name = 'save_user_preferences';
147
185
  description = 'Save user preferences for personalization';
148
-
186
+
149
187
  inputSchema = z.object({
150
188
  theme: z.enum(['light', 'dark']).optional(),
151
189
  language: z.string().optional(),
152
- notifications: z.boolean().optional()
190
+ notifications: z.boolean().optional(),
153
191
  });
154
-
192
+
155
193
  async execute(input: z.infer<typeof this.inputSchema>) {
156
194
  // Get current user
157
195
  const user = await User.get();
158
-
196
+
159
197
  // Update user data
160
198
  await user.update({
161
199
  preferences: {
162
200
  theme: input.theme,
163
201
  language: input.language,
164
- notifications: input.notifications
165
- }
202
+ notifications: input.notifications,
203
+ },
166
204
  });
167
-
205
+
168
206
  return {
169
207
  success: true,
170
208
  message: 'Preferences saved successfully',
171
- preferences: input
209
+ preferences: input,
172
210
  };
173
211
  }
174
212
  }
@@ -185,40 +223,40 @@ import { env } from 'lua-cli/skill';
185
223
  export default class WeatherTool implements LuaTool {
186
224
  name = 'get_weather';
187
225
  description = 'Get current weather for any city';
188
-
226
+
189
227
  inputSchema = z.object({
190
228
  city: z.string().describe('City name'),
191
- units: z.enum(['metric', 'imperial']).default('metric')
229
+ units: z.enum(['metric', 'imperial']).default('metric'),
192
230
  });
193
-
231
+
194
232
  outputSchema = z.object({
195
233
  temperature: z.number(),
196
234
  condition: z.string(),
197
235
  humidity: z.number(),
198
- city: z.string()
236
+ city: z.string(),
199
237
  });
200
-
238
+
201
239
  async execute(input: z.infer<typeof this.inputSchema>) {
202
240
  const apiKey = env('WEATHER_API_KEY');
203
-
241
+
204
242
  if (!apiKey) {
205
243
  throw new Error('WEATHER_API_KEY not configured');
206
244
  }
207
-
245
+
208
246
  try {
209
247
  const response = await axios.get('https://api.openweathermap.org/data/2.5/weather', {
210
248
  params: {
211
249
  q: input.city,
212
250
  appid: apiKey,
213
- units: input.units
214
- }
251
+ units: input.units,
252
+ },
215
253
  });
216
-
254
+
217
255
  return {
218
256
  temperature: response.data.main.temp,
219
257
  condition: response.data.weather[0].main,
220
258
  humidity: response.data.main.humidity,
221
- city: input.city
259
+ city: input.city,
222
260
  };
223
261
  } catch (error) {
224
262
  if (axios.isAxiosError(error)) {
@@ -239,36 +277,36 @@ import { z } from 'zod';
239
277
  export default class GetProductTool implements LuaTool {
240
278
  name = 'get_product';
241
279
  description = 'Retrieve a product by ID';
242
-
280
+
243
281
  inputSchema = z.object({
244
- productId: z.string()
282
+ productId: z.string(),
245
283
  });
246
-
284
+
247
285
  async execute(input: z.infer<typeof this.inputSchema>) {
248
286
  try {
249
287
  const product = await Products.getById(input.productId);
250
-
288
+
251
289
  if (!product) {
252
290
  return {
253
291
  success: false,
254
292
  error: 'Product not found',
255
- productId: input.productId
293
+ productId: input.productId,
256
294
  };
257
295
  }
258
-
296
+
259
297
  return {
260
298
  success: true,
261
299
  product: {
262
300
  id: product.id,
263
301
  name: product.name,
264
302
  price: product.price,
265
- stock: product.stock
266
- }
303
+ stock: product.stock,
304
+ },
267
305
  };
268
306
  } catch (error) {
269
307
  return {
270
308
  success: false,
271
- error: error instanceof Error ? error.message : 'Unknown error'
309
+ error: error instanceof Error ? error.message : 'Unknown error',
272
310
  };
273
311
  }
274
312
  }
@@ -284,66 +322,66 @@ import { z } from 'zod';
284
322
  export default class GameTool implements LuaTool {
285
323
  name = 'play_game';
286
324
  description = 'Play a number guessing game';
287
-
325
+
288
326
  inputSchema = z.object({
289
327
  action: z.enum(['start', 'guess', 'status']),
290
- guess: z.number().min(1).max(100).optional()
328
+ guess: z.number().min(1).max(100).optional(),
291
329
  });
292
-
330
+
293
331
  async execute(input: z.infer<typeof this.inputSchema>) {
294
332
  // Use Data API to persist game state
295
333
  const games = await Data.get('games');
296
334
  let gameState = games.length > 0 ? games[0].data : null;
297
-
335
+
298
336
  if (input.action === 'start') {
299
337
  // Start new game
300
338
  const secretNumber = Math.floor(Math.random() * 100) + 1;
301
339
  gameState = {
302
340
  secretNumber,
303
341
  attempts: 0,
304
- started: new Date()
342
+ started: new Date(),
305
343
  };
306
-
344
+
307
345
  await Data.create('games', gameState);
308
-
346
+
309
347
  return {
310
348
  message: 'Game started! Guess a number between 1 and 100',
311
- attempts: 0
349
+ attempts: 0,
312
350
  };
313
351
  }
314
-
352
+
315
353
  if (input.action === 'guess') {
316
354
  if (!gameState) {
317
355
  return { error: 'No active game. Use action: "start"' };
318
356
  }
319
-
357
+
320
358
  gameState.attempts++;
321
-
359
+
322
360
  if (input.guess === gameState.secretNumber) {
323
361
  await Data.delete('games', gameState.id);
324
362
  return {
325
363
  message: '🎉 Correct! You won!',
326
- attempts: gameState.attempts
364
+ attempts: gameState.attempts,
327
365
  };
328
366
  }
329
-
367
+
330
368
  const hint = input.guess! < gameState.secretNumber ? 'higher' : 'lower';
331
369
  await Data.update('games', gameState.id, gameState);
332
-
370
+
333
371
  return {
334
372
  message: `Try ${hint}`,
335
- attempts: gameState.attempts
373
+ attempts: gameState.attempts,
336
374
  };
337
375
  }
338
-
376
+
339
377
  // Status
340
378
  if (!gameState) {
341
379
  return { message: 'No active game' };
342
380
  }
343
-
381
+
344
382
  return {
345
383
  message: 'Game in progress',
346
- attempts: gameState.attempts
384
+ attempts: gameState.attempts,
347
385
  };
348
386
  }
349
387
  }
@@ -360,38 +398,40 @@ import { z } from 'zod';
360
398
  export default class ReminderTool implements LuaTool {
361
399
  name = 'set_reminder';
362
400
  description = 'Set a reminder to notify user later';
363
-
401
+
364
402
  inputSchema = z.object({
365
403
  message: z.string(),
366
- minutes: z.number().min(1).max(1440)
404
+ minutes: z.number().min(1).max(1440),
367
405
  });
368
-
406
+
369
407
  async execute(input: z.infer<typeof this.inputSchema>) {
370
408
  // Create a job that will notify the user later
371
409
  const job = await Jobs.create({
372
410
  name: `reminder-${Date.now()}`,
373
411
  description: 'User reminder',
374
412
  metadata: {
375
- message: input.message
413
+ message: input.message,
376
414
  },
377
415
  schedule: {
378
416
  type: 'once',
379
- executeAt: new Date(Date.now() + input.minutes * 60000)
417
+ executeAt: new Date(Date.now() + input.minutes * 60000),
380
418
  },
381
419
  execute: async (jobInstance: JobInstance) => {
382
420
  const user = await jobInstance.user();
383
- await user.send([{
384
- type: 'text',
385
- text: `⏰ Reminder: ${jobInstance.metadata.message}`
386
- }]);
421
+ await user.send([
422
+ {
423
+ type: 'text',
424
+ text: `⏰ Reminder: ${jobInstance.metadata.message}`,
425
+ },
426
+ ]);
387
427
  return { success: true };
388
- }
428
+ },
389
429
  });
390
-
430
+
391
431
  return {
392
432
  success: true,
393
433
  message: `Reminder set for ${input.minutes} minutes from now`,
394
- jobId: job.id
434
+ jobId: job.id,
395
435
  };
396
436
  }
397
437
  }
@@ -400,12 +440,14 @@ export default class ReminderTool implements LuaTool {
400
440
  ## Naming Conventions
401
441
 
402
442
  ### ✅ Good Names
443
+
403
444
  - `get_weather` - Action + noun, clear purpose
404
445
  - `create_order` - Verb + noun, describes action
405
446
  - `search_products` - Clear, specific
406
447
  - `calculate_tax` - Action verb, specific
407
448
 
408
449
  ### ❌ Bad Names
450
+
409
451
  - `weather` - Too vague, unclear if getting or setting
410
452
  - `doIt` - Meaningless
411
453
  - `tool1` - Not descriptive
@@ -414,15 +456,16 @@ export default class ReminderTool implements LuaTool {
414
456
  ## Best Practices
415
457
 
416
458
  ### 1. Single Responsibility
459
+
417
460
  Each tool should do one thing well:
418
461
 
419
462
  ```typescript
420
463
  // ✅ Good - Focused purpose
421
- class GetWeatherTool { }
422
- class GetForecastTool { }
464
+ class GetWeatherTool {}
465
+ class GetForecastTool {}
423
466
 
424
467
  // ❌ Bad - Too many responsibilities
425
- class WeatherAndNewsAndStocksTool { }
468
+ class WeatherAndNewsAndStocksTool {}
426
469
  ```
427
470
 
428
471
  ### 2. Descriptive Schemas
@@ -431,13 +474,13 @@ class WeatherAndNewsAndStocksTool { }
431
474
  // ✅ Good - With descriptions
432
475
  inputSchema = z.object({
433
476
  email: z.string().email().describe('User email address'),
434
- sendCopy: z.boolean().optional().describe('Send copy to user')
477
+ sendCopy: z.boolean().optional().describe('Send copy to user'),
435
478
  });
436
479
 
437
480
  // ❌ Bad - No context
438
481
  inputSchema = z.object({
439
482
  email: z.string(),
440
- sendCopy: z.boolean()
483
+ sendCopy: z.boolean(),
441
484
  });
442
485
  ```
443
486
 
@@ -518,4 +561,3 @@ test();
518
561
  - [Product Tools](../../template/src/skills/tools/ProductsTool.ts)
519
562
  - [Basket Tools](../../template/src/skills/tools/BasketTool.ts)
520
563
  - [Game Tracker](../../template/src/skills/tools/GameScoreTrackerTool.ts)
521
-
@@ -92,7 +92,7 @@ set `enforceSchemas: true`. Only a strict boolean `true` enforces.
92
92
  - Validation only: `execute` receives the original `event`. No coercion, defaults or key stripping are applied to `event.query`, `event.headers` or `event.body`.
93
93
  - Header names are lowercase, so keys in a `headerSchema` must be lowercase (`'x-probe'`, not `'X-Probe'`). Transport headers are present too, so avoid `z.strictObject` for headers.
94
94
  - Webhooks that also subscribe to events receive `body: { eventType, ...payload }`; make sure `bodySchema` allows `eventType`.
95
- - Event deliveries are checked too, and they arrive with EMPTY `query` and `headers`. A `querySchema` or `headerSchema` with required keys, or a `bodySchema` that rejects `eventType`, logs a warning on every event delivery. Only with `enforceSchemas: true` do those deliveries fail, get logged at ERROR and not retried. Keep these schemas permissive, or declare them only on webhooks that are never event-subscribed.
95
+ - Event deliveries (CLI that includes BAC-1551, after re-push): `querySchema` and `headerSchema` are NOT applied, because `query` and `headers` are always empty there; `bodySchema` is still validated, so make sure it allows `eventType`. A body violation logs a warning (or, with `enforceSchemas: true`, is logged at ERROR and not retried). The normal HTTP path is unchanged. Bundles built with an older CLI still check the empty query/headers on event deliveries.
96
96
  - Webhooks deployed from older CLI versions keep the old, unvalidated behaviour until they are re-compiled and re-pushed.
97
97
 
98
98
  ## Request Verification
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lua-cli",
3
- "version": "3.52.0",
3
+ "version": "3.54.0",
4
4
  "description": "Build, test, and deploy AI agents with custom tools, webhooks, and scheduled jobs. Features LuaAgent unified configuration, streaming chat, and batch deployment.",
5
5
  "readmeFilename": "README.md",
6
6
  "main": "dist/api-exports.js",
@@ -56,6 +56,8 @@ export default class GreetingTool implements LuaTool {
56
56
  }
57
57
  ```
58
58
 
59
+ Optional: add `label = { running: 'Greeting {name}', done: 'Greeted {name}' };` to choose what the chat shows while the tool runs and when it is done (by default it shows the tool's name). `{name}` is filled in from the call's arguments.
60
+
59
61
  ### 2. Create a skill to group your tools
60
62
 
61
63
  Create `src/skills/greeting.skill.ts`:
@@ -20,7 +20,7 @@
20
20
  "inquirer": "^12.9.6",
21
21
  "stripe": "^17.5.0",
22
22
  "js-yaml": "^4.1.0",
23
- "lua-cli": "^3.52.0",
23
+ "lua-cli": "^3.54.0",
24
24
  "openai": "^5.23.0",
25
25
  "uuid": "^13.0.0",
26
26
  "zod": "^3.24.1"
@@ -8,6 +8,11 @@ import { z } from 'zod';
8
8
  * and the admin console). Routes: the handlers below (run in the Lua sandbox
9
9
  * with the signed-in person as `auth`; Data, env() and fetch are available).
10
10
  *
11
+ * GET routes must only read. Someone you share this view with view-only
12
+ * (`auth.accessLevel === 'view_only'`) can call every GET route, with the
13
+ * agent's data, and nothing else; put every change in a POST, PUT, PATCH or
14
+ * DELETE route.
15
+ *
11
16
  * lua apps dev __APP_NAME__ pages with hot reload, routes run locally as you
12
17
  * lua test webapp --name __APP_NAME__ --route 'GET /hello'
13
18
  * lua push webapp --name __APP_NAME__
@@ -8,8 +8,9 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@base-ui/react": "~1.8.0",
11
- "@lua-ai-global/app-client": "~0.0.2",
12
- "@lua-ai-global/ui": "~0.2.0",
11
+ "@lua-ai-global/app-chat": "~0.0.1",
12
+ "@lua-ai-global/app-client": "~0.0.3",
13
+ "@lua-ai-global/ui": "~0.2.3",
13
14
  "@phosphor-icons/react": "~2.1.10",
14
15
  "react": "~19.3.0",
15
16
  "react-dom": "~19.3.0"
@@ -1,3 +1,5 @@
1
1
  @import '@lua-ai-global/ui/styles.css';
2
+ /* The agent pane's styles: after the design system's, on its tokens. */
3
+ @import '@lua-ai-global/app-chat/styles.css';
2
4
  /* Tailwind scans the design system's compiled components for the classes they use. */
3
5
  @source '../node_modules/@lua-ai-global/ui/dist';
@@ -2,6 +2,7 @@ import { StrictMode } from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
3
  import { App } from './App';
4
4
  import { lua } from '@lua-ai-global/app-client';
5
+ import { AgentPaneLayout } from '@lua-ai-global/app-chat';
5
6
  import './index.css';
6
7
 
7
8
  // Theme follows the shell: first from the bootstrap, then from the host's `init`.
@@ -10,7 +11,10 @@ lua.onInit((init) => document.documentElement.classList.toggle('dark', init.them
10
11
 
11
12
  createRoot(document.getElementById('root')!).render(
12
13
  <StrictMode>
13
- <App />
14
+ {/* The agent pane: the person's own conversation with this app's agent, docked beside the page. */}
15
+ <AgentPaneLayout>
16
+ <App />
17
+ </AgentPaneLayout>
14
18
  </StrictMode>
15
19
  );
16
20
  void lua.start();