lua-cli 3.53.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.
- package/dist/api-exports.d.ts +81 -1
- package/dist/api-exports.js +385 -32
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +2006 -580
- package/dist/index.js.map +1 -1
- package/dist/voice/test/index.d.ts +19 -0
- package/dist/workflow-builder.d.ts +19 -0
- package/dist/workflow-builder.js +333 -7
- package/dist/workflow-builder.js.map +1 -1
- package/docs/API_REFERENCE.md +23 -0
- package/docs/CLI_REFERENCE.md +14 -0
- package/docs/README.md +3 -3
- package/docs/api/LuaTool.md +124 -82
- package/docs/api/LuaWebhook.md +1 -1
- package/package.json +2 -2
- package/template/README.md +2 -0
- package/template/package.json +1 -1
- package/template-web-app/app.ts +5 -0
package/docs/API_REFERENCE.md
CHANGED
|
@@ -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
|
|
package/docs/CLI_REFERENCE.md
CHANGED
|
@@ -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.
|
|
1
|
+
# lua-cli v3.54.0
|
|
2
2
|
|
|
3
|
-
Welcome to the comprehensive API documentation for lua-cli v3.
|
|
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.
|
|
266
|
+
**Version:** 3.54.0
|
|
267
267
|
**Last Updated:** October 2026
|
|
268
268
|
|
|
269
269
|
---
|
package/docs/api/LuaTool.md
CHANGED
|
@@ -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
|
-
|
|
385
|
-
|
|
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
|
-
|
package/docs/api/LuaWebhook.md
CHANGED
|
@@ -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
|
|
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.
|
|
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",
|
|
@@ -116,8 +116,8 @@
|
|
|
116
116
|
"tsup": "^8.5.1",
|
|
117
117
|
"@lua/sandbox-runtime": "0.0.1",
|
|
118
118
|
"@lua/shared-sandbox": "0.0.1",
|
|
119
|
-
"@lua/shared-types": "0.0.1",
|
|
120
119
|
"@lua/shared-source-sync": "0.0.1",
|
|
120
|
+
"@lua/shared-types": "0.0.1",
|
|
121
121
|
"@lua/workflow-graph": "0.0.1"
|
|
122
122
|
},
|
|
123
123
|
"scripts": {
|
package/template/README.md
CHANGED
|
@@ -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`:
|
package/template/package.json
CHANGED
package/template-web-app/app.ts
CHANGED
|
@@ -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__
|