lua-cli 3.1.0-alpha.4 → 3.1.0-alpha.5
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/cdn.api.service.d.ts +18 -0
- package/dist/api/cdn.api.service.js +43 -0
- package/dist/api/custom.data.api.service.d.ts +4 -3
- package/dist/api/custom.data.api.service.js +4 -3
- package/dist/api/developer.api.service.d.ts +54 -1
- package/dist/api/developer.api.service.js +89 -0
- package/dist/api/job.api.service.d.ts +10 -0
- package/dist/api/job.api.service.js +14 -0
- package/dist/api/lazy-instances.d.ts +8 -0
- package/dist/api/lazy-instances.js +16 -0
- package/dist/api/postprocessor.api.service.d.ts +3 -6
- package/dist/api/postprocessor.api.service.js +2 -3
- package/dist/api-exports.d.ts +74 -6
- package/dist/api-exports.js +87 -7
- package/dist/cli/command-definitions.js +34 -7
- package/dist/commands/admin.js +1 -1
- package/dist/commands/channels.js +1 -1
- package/dist/commands/compile.js +23 -4
- package/dist/commands/evals.d.ts +8 -0
- package/dist/commands/evals.js +41 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/init.d.ts +10 -1
- package/dist/commands/init.js +13 -3
- package/dist/commands/mcp.d.ts +18 -0
- package/dist/commands/mcp.js +393 -0
- package/dist/commands/push.js +172 -14
- package/dist/common/data.entry.instance.d.ts +1 -1
- package/dist/common/data.entry.instance.js +4 -4
- package/dist/common/job.instance.d.ts +24 -0
- package/dist/common/job.instance.js +38 -0
- package/dist/config/constants.d.ts +1 -0
- package/dist/config/constants.js +1 -0
- package/dist/index.js +1 -0
- package/dist/interfaces/cdn.d.ts +24 -0
- package/dist/interfaces/cdn.js +5 -0
- package/dist/interfaces/compile.d.ts +1 -0
- package/dist/interfaces/custom.data.d.ts +3 -3
- package/dist/interfaces/index.d.ts +1 -0
- package/dist/interfaces/mcp.d.ts +64 -0
- package/dist/interfaces/mcp.js +5 -0
- package/dist/types/api-contracts.d.ts +36 -14
- package/dist/types/compile.types.d.ts +5 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +3 -1
- package/dist/types/skill.d.ts +120 -13
- package/dist/types/skill.js +95 -5
- package/dist/utils/bundling.d.ts +4 -11
- package/dist/utils/bundling.js +19 -27
- package/dist/utils/compile.d.ts +17 -8
- package/dist/utils/compile.js +71 -37
- package/dist/utils/deployment.js +13 -6
- package/dist/utils/dev-api.js +1 -2
- package/dist/utils/dev-server.js +1 -1
- package/dist/utils/files.d.ts +8 -1
- package/dist/utils/files.js +13 -2
- package/dist/utils/init-helpers.d.ts +3 -1
- package/dist/utils/init-helpers.js +7 -2
- package/dist/utils/mcp-server-management.d.ts +23 -0
- package/dist/utils/mcp-server-management.js +212 -0
- package/dist/utils/sandbox.d.ts +4 -2
- package/dist/utils/sandbox.js +22 -3
- package/dist/web/app.css +1505 -14
- package/dist/web/app.js +79 -64
- package/package.json +2 -6
- package/template/QUICKSTART.md +57 -761
- package/template/README.md +80 -906
- package/template/examples/README.md +106 -0
- package/template/{src → examples}/jobs/AbandonedBasketProcessorJob.ts +67 -11
- package/template/{src → examples}/postprocessors/modifyResponse.ts +3 -3
- package/template/{src → examples}/skills/tools/GameScoreTrackerTool.ts +11 -15
- package/template/{src → examples}/skills/tools/OrderTool.ts +25 -0
- package/template/examples/skills/tools/PremiumFeatureTool.ts +98 -0
- package/template/{src → examples}/skills/tools/UserDataTool.ts +34 -0
- package/template/examples/webhooks/FileUploadWebhook.ts +86 -0
- package/template/package-lock.json +7895 -0
- package/template/package.json +1 -1
- package/template/src/index.ts +40 -22
- /package/template/{src → examples}/jobs/DailyCleanupJob.ts +0 -0
- /package/template/{src → examples}/jobs/DataMigrationJob.ts +0 -0
- /package/template/{src → examples}/jobs/HealthCheckJob.ts +0 -0
- /package/template/{src → examples}/preprocessors/messageMatching.ts +0 -0
- /package/template/{src → examples}/services/ApiService.ts +0 -0
- /package/template/{src → examples}/services/GetWeather.ts +0 -0
- /package/template/{src → examples}/skills/basket.skill.ts +0 -0
- /package/template/{src → examples}/skills/product.skill.ts +0 -0
- /package/template/{src → examples}/skills/tools/BasketTool.ts +0 -0
- /package/template/{src → examples}/skills/tools/CreateInlineJob.ts +0 -0
- /package/template/{src → examples}/skills/tools/CreatePostTool.ts +0 -0
- /package/template/{src → examples}/skills/tools/CustomDataTool.ts +0 -0
- /package/template/{src → examples}/skills/tools/GetWeatherTool.ts +0 -0
- /package/template/{src → examples}/skills/tools/PaymentTool.ts +0 -0
- /package/template/{src → examples}/skills/tools/ProductsTool.ts +0 -0
- /package/template/{src → examples}/skills/tools/SmartBasketTool.ts +0 -0
- /package/template/{src → examples}/skills/user.skill.ts +0 -0
- /package/template/{src → examples}/webhooks/PaymentWebhook.ts +0 -0
- /package/template/{src → examples}/webhooks/UserEventWebhook.ts +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# 📚 Example Code
|
|
2
|
+
|
|
3
|
+
This folder contains example implementations of all Lua AI Agent features. Use these as reference when building your own agent.
|
|
4
|
+
|
|
5
|
+
## 📁 What's Inside
|
|
6
|
+
|
|
7
|
+
### skills/
|
|
8
|
+
Example skills and tools demonstrating platform APIs:
|
|
9
|
+
|
|
10
|
+
| File | What it shows |
|
|
11
|
+
|------|--------------|
|
|
12
|
+
| `tools/GetWeatherTool.ts` | External API integration |
|
|
13
|
+
| `tools/UserDataTool.ts` | User API - get/update user data, chat history, AI generation |
|
|
14
|
+
| `tools/ProductsTool.ts` | Products API - CRUD operations |
|
|
15
|
+
| `tools/BasketTool.ts` | Baskets API - shopping cart |
|
|
16
|
+
| `tools/OrderTool.ts` | Orders API - order management, status & data updates |
|
|
17
|
+
| `tools/CustomDataTool.ts` | Data API - custom collections with semantic search |
|
|
18
|
+
| `tools/PaymentTool.ts` | Stripe integration, environment variables |
|
|
19
|
+
| `tools/SmartBasketTool.ts` | Dynamic job creation from tools |
|
|
20
|
+
| `tools/GameScoreTrackerTool.ts` | Jobs API - interval jobs, deactivation, getAll |
|
|
21
|
+
|
|
22
|
+
### webhooks/
|
|
23
|
+
HTTP endpoints for external integrations:
|
|
24
|
+
|
|
25
|
+
| File | What it shows |
|
|
26
|
+
|------|--------------|
|
|
27
|
+
| `PaymentWebhook.ts` | Stripe payment notifications, Orders API |
|
|
28
|
+
| `UserEventWebhook.ts` | External events, **Templates API** (WhatsApp) |
|
|
29
|
+
| `FileUploadWebhook.ts` | **CDN API** - file uploads, Data API |
|
|
30
|
+
|
|
31
|
+
### jobs/
|
|
32
|
+
Scheduled background tasks:
|
|
33
|
+
|
|
34
|
+
| File | What it shows |
|
|
35
|
+
|------|--------------|
|
|
36
|
+
| `HealthCheckJob.ts` | Interval schedule, health monitoring |
|
|
37
|
+
| `DailyCleanupJob.ts` | Cron schedule, data cleanup |
|
|
38
|
+
| `DataMigrationJob.ts` | One-time schedule, batch processing |
|
|
39
|
+
| `AbandonedBasketProcessorJob.ts` | **Templates API** (WhatsApp), batch reminders |
|
|
40
|
+
|
|
41
|
+
### preprocessors/
|
|
42
|
+
Message filtering before the agent:
|
|
43
|
+
|
|
44
|
+
| File | What it shows |
|
|
45
|
+
|------|--------------|
|
|
46
|
+
| `messageMatching.ts` | Modify/block messages based on content |
|
|
47
|
+
|
|
48
|
+
### postprocessors/
|
|
49
|
+
Response transformation after the agent:
|
|
50
|
+
|
|
51
|
+
| File | What it shows |
|
|
52
|
+
|------|--------------|
|
|
53
|
+
| `modifyResponse.ts` | Transform agent responses |
|
|
54
|
+
|
|
55
|
+
### services/
|
|
56
|
+
Helper utilities:
|
|
57
|
+
|
|
58
|
+
| File | What it shows |
|
|
59
|
+
|------|--------------|
|
|
60
|
+
| `ApiService.ts` | HTTP client wrapper |
|
|
61
|
+
| `GetWeather.ts` | Mock service for testing |
|
|
62
|
+
|
|
63
|
+
## 🎯 API Coverage
|
|
64
|
+
|
|
65
|
+
| API | Where it's demonstrated |
|
|
66
|
+
|-----|------------------------|
|
|
67
|
+
| **User** | `UserDataTool.ts` |
|
|
68
|
+
| **Products** | `ProductsTool.ts` |
|
|
69
|
+
| **Baskets** | `BasketTool.ts`, `SmartBasketTool.ts` |
|
|
70
|
+
| **Orders** | `OrderTool.ts`, `PaymentWebhook.ts` |
|
|
71
|
+
| **Data** | `CustomDataTool.ts`, `FileUploadWebhook.ts` |
|
|
72
|
+
| **Jobs** | `SmartBasketTool.ts`, `GameScoreTrackerTool.ts` |
|
|
73
|
+
| **AI** | `UserDataTool.ts` |
|
|
74
|
+
| **Templates** | `UserEventWebhook.ts`, `AbandonedBasketProcessorJob.ts` |
|
|
75
|
+
| **CDN** | `FileUploadWebhook.ts` |
|
|
76
|
+
|
|
77
|
+
## 🚀 How to Use
|
|
78
|
+
|
|
79
|
+
### Option 1: Copy what you need
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Copy a tool
|
|
83
|
+
cp examples/skills/tools/GetWeatherTool.ts src/skills/tools/
|
|
84
|
+
|
|
85
|
+
# Copy a webhook
|
|
86
|
+
cp examples/webhooks/PaymentWebhook.ts src/webhooks/
|
|
87
|
+
|
|
88
|
+
# Copy a job
|
|
89
|
+
cp examples/jobs/HealthCheckJob.ts src/jobs/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Option 2: Move entire directories
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
mv examples/skills src/
|
|
96
|
+
mv examples/webhooks src/
|
|
97
|
+
mv examples/jobs src/
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Then import them in your `src/index.ts`.
|
|
101
|
+
|
|
102
|
+
## 📖 Documentation
|
|
103
|
+
|
|
104
|
+
- **API Reference:** https://docs.heylua.ai/api
|
|
105
|
+
- **Examples Guide:** https://docs.heylua.ai/examples
|
|
106
|
+
- **Best Practices:** https://docs.heylua.ai/template/best-practices
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Abandoned Basket Processor Job
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Works together with CreateSmartBasketTool to send abandoned cart notifications.
|
|
4
|
+
* Processes abandoned cart reminders and sends WhatsApp notifications.
|
|
6
5
|
*
|
|
7
6
|
* Flow:
|
|
8
7
|
* 1. User creates basket → Tool schedules reminder in custom data
|
|
9
8
|
* 2. This job runs every 15 minutes
|
|
10
9
|
* 3. Checks which reminders are due
|
|
11
10
|
* 4. Checks if baskets were checked out
|
|
12
|
-
* 5. Sends
|
|
11
|
+
* 5. Sends WhatsApp template message for abandoned baskets
|
|
12
|
+
*
|
|
13
|
+
* Demonstrates:
|
|
14
|
+
* - Interval job scheduling
|
|
15
|
+
* - Batch processing pattern
|
|
16
|
+
* - Templates API integration (WhatsApp)
|
|
17
|
+
* - Data API for tracking state
|
|
13
18
|
*/
|
|
14
19
|
|
|
15
|
-
import { LuaJob, Data, Baskets } from "lua-cli";
|
|
20
|
+
import { LuaJob, Data, Baskets, Templates, env } from "lua-cli";
|
|
16
21
|
|
|
17
22
|
const abandonedBasketProcessorJob = new LuaJob({
|
|
18
23
|
name: "process-basket-reminders",
|
|
19
|
-
description: "Processes abandoned basket reminders",
|
|
24
|
+
description: "Processes abandoned basket reminders and sends WhatsApp notifications",
|
|
20
25
|
|
|
21
26
|
// Run every 15 minutes
|
|
22
27
|
schedule: {
|
|
@@ -27,6 +32,10 @@ const abandonedBasketProcessorJob = new LuaJob({
|
|
|
27
32
|
timeout: 120, // 2 minutes
|
|
28
33
|
|
|
29
34
|
execute: async () => {
|
|
35
|
+
// Configuration - set these in your environment variables
|
|
36
|
+
const WHATSAPP_CHANNEL_ID = env('WHATSAPP_CHANNEL_ID') || '';
|
|
37
|
+
const ABANDONED_CART_TEMPLATE_NAME = 'abandoned_cart_reminder';
|
|
38
|
+
|
|
30
39
|
console.log('🔍 Processing basket reminders...');
|
|
31
40
|
|
|
32
41
|
// Get all pending reminders
|
|
@@ -37,6 +46,7 @@ const abandonedBasketProcessorJob = new LuaJob({
|
|
|
37
46
|
let processedCount = 0;
|
|
38
47
|
let abandonedCount = 0;
|
|
39
48
|
let checkedOutCount = 0;
|
|
49
|
+
let messagesSent = 0;
|
|
40
50
|
|
|
41
51
|
for (const reminder of reminders) {
|
|
42
52
|
// Skip if not yet time
|
|
@@ -72,9 +82,58 @@ const abandonedBasketProcessorJob = new LuaJob({
|
|
|
72
82
|
totalValue,
|
|
73
83
|
items: basket.items,
|
|
74
84
|
abandonedAt: new Date().toISOString(),
|
|
75
|
-
reminderSent:
|
|
85
|
+
reminderSent: false
|
|
76
86
|
});
|
|
77
87
|
|
|
88
|
+
// Send WhatsApp reminder if we have a phone number
|
|
89
|
+
if (reminder.data.phoneNumber && WHATSAPP_CHANNEL_ID) {
|
|
90
|
+
try {
|
|
91
|
+
// Find the abandoned cart template
|
|
92
|
+
const templatesResponse = await Templates.whatsapp.list(WHATSAPP_CHANNEL_ID, {
|
|
93
|
+
search: ABANDONED_CART_TEMPLATE_NAME
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const template = templatesResponse.templates.find(
|
|
97
|
+
t => t.status === 'APPROVED'
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (template) {
|
|
101
|
+
// Send the template message
|
|
102
|
+
const result = await Templates.whatsapp.send(WHATSAPP_CHANNEL_ID, template.id, {
|
|
103
|
+
phoneNumbers: [reminder.data.phoneNumber],
|
|
104
|
+
values: {
|
|
105
|
+
body: {
|
|
106
|
+
item_count: String(basket.items?.length || 0),
|
|
107
|
+
total_value: `${basket.currency} ${totalValue.toFixed(2)}`
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (result.totalErrors === 0) {
|
|
113
|
+
console.log(`📱 WhatsApp reminder sent to ${reminder.data.phoneNumber}`);
|
|
114
|
+
messagesSent++;
|
|
115
|
+
|
|
116
|
+
// Update the abandoned basket record
|
|
117
|
+
const abandonedRecords = await Data.get('abandoned-baskets', {}, 1, 100);
|
|
118
|
+
const record = abandonedRecords.data?.find((r: any) => r.data.basketId === basket.id);
|
|
119
|
+
if (record) {
|
|
120
|
+
await Data.update('abandoned-baskets', record.id, {
|
|
121
|
+
...record.data,
|
|
122
|
+
reminderSent: true,
|
|
123
|
+
reminderSentAt: new Date().toISOString()
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
console.warn(`⚠️ Failed to send WhatsApp: ${result.results[0]?.error}`);
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
console.warn(`⚠️ No approved template found: ${ABANDONED_CART_TEMPLATE_NAME}`);
|
|
131
|
+
}
|
|
132
|
+
} catch (templateError) {
|
|
133
|
+
console.error('Failed to send WhatsApp template:', templateError);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
78
137
|
// Update reminder status
|
|
79
138
|
await Data.update('basket-reminders', reminder.id, {
|
|
80
139
|
...reminder.data,
|
|
@@ -83,9 +142,6 @@ const abandonedBasketProcessorJob = new LuaJob({
|
|
|
83
142
|
processedAt: new Date().toISOString()
|
|
84
143
|
});
|
|
85
144
|
|
|
86
|
-
// TODO: Send actual reminder email/SMS
|
|
87
|
-
console.log(`📧 Would send reminder: "You left ${basket.items?.length || 0} items in your cart!"`);
|
|
88
|
-
|
|
89
145
|
} else {
|
|
90
146
|
// Basket was checked out
|
|
91
147
|
console.log(`✅ Basket ${basket.id} was checked out (${basket.status})`);
|
|
@@ -120,17 +176,17 @@ const abandonedBasketProcessorJob = new LuaJob({
|
|
|
120
176
|
}
|
|
121
177
|
|
|
122
178
|
console.log('✅ Reminder processing complete');
|
|
123
|
-
console.log(`📊 Processed: ${processedCount}, Abandoned: ${abandonedCount}, Checked out: ${checkedOutCount}`);
|
|
179
|
+
console.log(`📊 Processed: ${processedCount}, Abandoned: ${abandonedCount}, Checked out: ${checkedOutCount}, Messages sent: ${messagesSent}`);
|
|
124
180
|
|
|
125
181
|
return {
|
|
126
182
|
success: true,
|
|
127
183
|
processedCount,
|
|
128
184
|
abandonedCount,
|
|
129
185
|
checkedOutCount,
|
|
186
|
+
messagesSent,
|
|
130
187
|
timestamp: new Date().toISOString()
|
|
131
188
|
};
|
|
132
189
|
}
|
|
133
190
|
});
|
|
134
191
|
|
|
135
192
|
export default abandonedBasketProcessorJob;
|
|
136
|
-
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PostProcessor, UserDataInstance } from "lua-cli";
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
const modifyResponsePostProcessor = new PostProcessor({
|
|
@@ -11,9 +11,9 @@ const modifyResponsePostProcessor = new PostProcessor({
|
|
|
11
11
|
console.log("Response", response);
|
|
12
12
|
console.log("Channel", channel);
|
|
13
13
|
if (response.includes("test")) {
|
|
14
|
-
return message.toUpperCase();
|
|
14
|
+
return { modifiedResponse: message.toUpperCase() };
|
|
15
15
|
}
|
|
16
|
-
return response;
|
|
16
|
+
return { modifiedResponse: response };
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -101,8 +101,8 @@ export class TrackGameScoresTool implements LuaTool {
|
|
|
101
101
|
if (gameData.data.status === 'finished') {
|
|
102
102
|
console.log('🏁 Game ' + gameId + ' is already finished. Stopping job.');
|
|
103
103
|
|
|
104
|
-
// IMPORTANT: Job stops itself!
|
|
105
|
-
await
|
|
104
|
+
// IMPORTANT: Job stops itself using the instance method!
|
|
105
|
+
await job.deactivate();
|
|
106
106
|
|
|
107
107
|
return {
|
|
108
108
|
action: 'job-stopped',
|
|
@@ -168,8 +168,8 @@ export class TrackGameScoresTool implements LuaTool {
|
|
|
168
168
|
finishedAt: new Date().toISOString()
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
-
// IMPORTANT: Job stops itself when game ends!
|
|
172
|
-
await
|
|
171
|
+
// IMPORTANT: Job stops itself when game ends using instance method!
|
|
172
|
+
await job.deactivate();
|
|
173
173
|
|
|
174
174
|
console.log('⏹️ Score tracking job stopped for game ' + gameId);
|
|
175
175
|
|
|
@@ -208,7 +208,7 @@ export class TrackGameScoresTool implements LuaTool {
|
|
|
208
208
|
}
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
-
console.log(`✅ Score tracking job created: ${job.
|
|
211
|
+
console.log(`✅ Score tracking job created: ${job.id}`);
|
|
212
212
|
console.log(`📊 Job will update scores every ${input.updateIntervalSeconds} seconds`);
|
|
213
213
|
console.log(`⏹️ Job will automatically stop when game ends`);
|
|
214
214
|
|
|
@@ -222,7 +222,7 @@ export class TrackGameScoresTool implements LuaTool {
|
|
|
222
222
|
status: 'scheduled'
|
|
223
223
|
},
|
|
224
224
|
scoreTracker: {
|
|
225
|
-
jobId: job.
|
|
225
|
+
jobId: job.id,
|
|
226
226
|
jobName: job.name,
|
|
227
227
|
updateInterval: input.updateIntervalSeconds,
|
|
228
228
|
startsAt: jobStartTime.toISOString(),
|
|
@@ -309,14 +309,10 @@ export class StopGameTrackingTool implements LuaTool {
|
|
|
309
309
|
async execute(input: { gameId: string }) {
|
|
310
310
|
console.log(`⏹️ Stopping score tracking for game ${input.gameId}...`);
|
|
311
311
|
|
|
312
|
-
// Find the tracking job
|
|
313
|
-
const allJobs = await Jobs.getAll();
|
|
314
|
-
|
|
315
|
-
if (!allJobs.success || !allJobs.data) {
|
|
316
|
-
throw new Error('Failed to fetch jobs');
|
|
317
|
-
}
|
|
312
|
+
// Find the tracking job - Jobs.getAll() returns array of JobInstance
|
|
313
|
+
const allJobs = await Jobs.getAll({ includeDynamic: true });
|
|
318
314
|
|
|
319
|
-
const trackingJob = allJobs.
|
|
315
|
+
const trackingJob = allJobs.find((j: any) =>
|
|
320
316
|
j.name === `track-game-${input.gameId}`
|
|
321
317
|
);
|
|
322
318
|
|
|
@@ -328,8 +324,8 @@ export class StopGameTrackingTool implements LuaTool {
|
|
|
328
324
|
};
|
|
329
325
|
}
|
|
330
326
|
|
|
331
|
-
// Deactivate the job
|
|
332
|
-
|
|
327
|
+
// Deactivate the job using instance method
|
|
328
|
+
await trackingJob.deactivate();
|
|
333
329
|
|
|
334
330
|
// Update game status
|
|
335
331
|
const games = await Data.get('games', {}, 1, 100);
|
|
@@ -52,3 +52,28 @@ export class GetOrderByIdTool implements LuaTool {
|
|
|
52
52
|
return Orders.getById(input.orderId);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Update custom data on an order.
|
|
58
|
+
* Useful for adding tracking info, notes, or custom metadata.
|
|
59
|
+
*/
|
|
60
|
+
export class UpdateOrderDataTool implements LuaTool {
|
|
61
|
+
name = "update_order_data";
|
|
62
|
+
description = "Update custom data on an order (tracking info, notes, etc.)";
|
|
63
|
+
inputSchema = z.object({
|
|
64
|
+
orderId: z.string().describe("The order ID to update"),
|
|
65
|
+
data: z.record(z.any()).describe("Custom data to add/update on the order")
|
|
66
|
+
});
|
|
67
|
+
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
68
|
+
console.log(`📝 Updating order ${input.orderId} with data:`, input.data);
|
|
69
|
+
|
|
70
|
+
const result = await Orders.updateData(input.data, input.orderId);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
orderId: input.orderId,
|
|
75
|
+
message: "Order data updated successfully",
|
|
76
|
+
updatedData: input.data
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { LuaTool, Products, User } from "lua-cli";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Example tool with a condition.
|
|
6
|
+
*
|
|
7
|
+
* The `condition` function determines if this tool should be available to the LLM.
|
|
8
|
+
* It runs BEFORE the tool is offered, so the LLM won't see this tool if condition returns false.
|
|
9
|
+
*
|
|
10
|
+
* Use cases:
|
|
11
|
+
* - Premium/paid features only for subscribed users
|
|
12
|
+
* - Features based on user verification status
|
|
13
|
+
* - Region-specific tools
|
|
14
|
+
* - A/B testing tool availability
|
|
15
|
+
* - Time-based feature access
|
|
16
|
+
*
|
|
17
|
+
* The condition has access to all Platform APIs: User, Data, Products, Baskets, Orders, etc.
|
|
18
|
+
*/
|
|
19
|
+
export default class PremiumFeatureTool implements LuaTool {
|
|
20
|
+
name = "premium_advanced_search";
|
|
21
|
+
description = "Advanced search with filters and sorting - available to premium users only";
|
|
22
|
+
|
|
23
|
+
inputSchema = z.object({
|
|
24
|
+
query: z.string().describe("Search query"),
|
|
25
|
+
filters: z.object({
|
|
26
|
+
category: z.string().optional().describe("Filter by category"),
|
|
27
|
+
minPrice: z.number().optional().describe("Minimum price filter"),
|
|
28
|
+
maxPrice: z.number().optional().describe("Maximum price filter"),
|
|
29
|
+
}).optional().describe("Optional filters"),
|
|
30
|
+
sortBy: z.enum(["relevance", "price_asc", "price_desc", "newest"]).optional().describe("Sort order")
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Condition function - determines if this tool should be available.
|
|
35
|
+
*
|
|
36
|
+
* This runs once per request when tools are being built.
|
|
37
|
+
* Return true to enable the tool, false to hide it from the LLM.
|
|
38
|
+
*
|
|
39
|
+
* If condition throws an error, the tool is disabled (fail-closed behavior).
|
|
40
|
+
*/
|
|
41
|
+
condition = async () => {
|
|
42
|
+
// Get the current user's data
|
|
43
|
+
const user = await User.get();
|
|
44
|
+
|
|
45
|
+
// Check if user has premium subscription
|
|
46
|
+
// The 'data' field contains custom user data stored via User.update()
|
|
47
|
+
const isPremium = user.data?.subscription === "premium" || user.data?.isPremium === true;
|
|
48
|
+
|
|
49
|
+
// You can also check other conditions:
|
|
50
|
+
// - user.data?.trialEndsAt > Date.now() (trial period)
|
|
51
|
+
// - user.country?.code === "US" (region-based)
|
|
52
|
+
// - user.data?.featureFlags?.advancedSearch (feature flags)
|
|
53
|
+
|
|
54
|
+
return isPremium;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
58
|
+
// This only runs if condition returned true
|
|
59
|
+
const { query, filters, sortBy } = input;
|
|
60
|
+
|
|
61
|
+
// Example: Search products with advanced filters
|
|
62
|
+
const searchOptions: any = {
|
|
63
|
+
query,
|
|
64
|
+
limit: 20,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (filters?.category) {
|
|
68
|
+
searchOptions.category = filters.category;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (sortBy) {
|
|
72
|
+
searchOptions.sortBy = sortBy;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const searchResult = await Products.search(searchOptions);
|
|
76
|
+
const products = searchResult.products;
|
|
77
|
+
|
|
78
|
+
// Apply price filters if provided
|
|
79
|
+
let filteredResults = products;
|
|
80
|
+
if (filters?.minPrice !== undefined || filters?.maxPrice !== undefined) {
|
|
81
|
+
filteredResults = products.filter((product) => {
|
|
82
|
+
const price = product.price || 0;
|
|
83
|
+
if (filters.minPrice !== undefined && price < filters.minPrice) return false;
|
|
84
|
+
if (filters.maxPrice !== undefined && price > filters.maxPrice) return false;
|
|
85
|
+
return true;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
query,
|
|
91
|
+
totalResults: filteredResults.length,
|
|
92
|
+
results: filteredResults.slice(0, 10), // Return top 10
|
|
93
|
+
appliedFilters: filters,
|
|
94
|
+
sortedBy: sortBy || "relevance"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
@@ -46,6 +46,40 @@ export class UpdateUserDataTool implements LuaTool {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Get the conversation history for the current user.
|
|
51
|
+
* Useful for analyzing past interactions or providing context.
|
|
52
|
+
*/
|
|
53
|
+
export class GetChatHistoryTool implements LuaTool {
|
|
54
|
+
name = "get_chat_history";
|
|
55
|
+
description = "Get the conversation history for the current user";
|
|
56
|
+
inputSchema = z.object({
|
|
57
|
+
limit: z.number().optional().describe("Maximum number of messages to retrieve")
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
61
|
+
const history = await User.getChatHistory();
|
|
62
|
+
|
|
63
|
+
// Optionally limit the results
|
|
64
|
+
const messages = input.limit ? history.slice(-input.limit) : history;
|
|
65
|
+
|
|
66
|
+
// Format the history for easier reading
|
|
67
|
+
const formattedHistory = messages.map(msg => ({
|
|
68
|
+
role: msg.role,
|
|
69
|
+
content: msg.content,
|
|
70
|
+
createdAt: msg.createdAt,
|
|
71
|
+
id: msg.id
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
totalMessages: history.length,
|
|
77
|
+
returnedMessages: messages.length,
|
|
78
|
+
history: formattedHistory
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
49
83
|
export class WritePoemTool implements LuaTool {
|
|
50
84
|
name = "write_poem";
|
|
51
85
|
description = "Write a poem about a given topic";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Upload Webhook
|
|
3
|
+
*
|
|
4
|
+
* Receives file uploads from external systems and stores them in the CDN.
|
|
5
|
+
*
|
|
6
|
+
* Use cases:
|
|
7
|
+
* - Customer uploads documents via your website form
|
|
8
|
+
* - Integration with document management systems
|
|
9
|
+
* - Receiving images/files from third-party services
|
|
10
|
+
*
|
|
11
|
+
* Webhook URL: https://webhook.heylua.ai/{agentId}/file-upload
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { LuaWebhook, CDN, Data, User } from "lua-cli";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
const fileUploadWebhook = new LuaWebhook({
|
|
18
|
+
name: "file-upload",
|
|
19
|
+
description: "Receives file uploads and stores them in CDN",
|
|
20
|
+
|
|
21
|
+
headerSchema: z.object({
|
|
22
|
+
'content-type': z.string(),
|
|
23
|
+
'x-api-key': z.string().optional()
|
|
24
|
+
}),
|
|
25
|
+
|
|
26
|
+
bodySchema: z.object({
|
|
27
|
+
filename: z.string(),
|
|
28
|
+
contentType: z.string(),
|
|
29
|
+
data: z.string().describe("Base64 encoded file data"),
|
|
30
|
+
metadata: z.object({
|
|
31
|
+
category: z.string().optional(),
|
|
32
|
+
description: z.string().optional(),
|
|
33
|
+
tags: z.array(z.string()).optional()
|
|
34
|
+
}).optional()
|
|
35
|
+
}),
|
|
36
|
+
|
|
37
|
+
execute: async (event: any) => {
|
|
38
|
+
const { body } = event;
|
|
39
|
+
console.log(`📁 Receiving file upload: ${body.filename}`);
|
|
40
|
+
|
|
41
|
+
// Decode base64 data
|
|
42
|
+
const buffer = Buffer.from(body.data, 'base64');
|
|
43
|
+
const blob = new Blob([buffer], { type: body.contentType });
|
|
44
|
+
const file = new File([blob], body.filename, { type: body.contentType });
|
|
45
|
+
|
|
46
|
+
console.log(`📤 Uploading to CDN: ${file.name} (${file.size} bytes)`);
|
|
47
|
+
|
|
48
|
+
// Upload to CDN
|
|
49
|
+
const fileId = await CDN.upload(file);
|
|
50
|
+
|
|
51
|
+
console.log(`✅ File stored with ID: ${fileId}`);
|
|
52
|
+
|
|
53
|
+
// Store file metadata in custom data for later retrieval
|
|
54
|
+
const fileRecord = await Data.create('uploaded-files', {
|
|
55
|
+
fileId: fileId,
|
|
56
|
+
filename: body.filename,
|
|
57
|
+
contentType: body.contentType,
|
|
58
|
+
size: file.size,
|
|
59
|
+
category: body.metadata?.category || 'uncategorized',
|
|
60
|
+
description: body.metadata?.description,
|
|
61
|
+
tags: body.metadata?.tags || [],
|
|
62
|
+
uploadedAt: new Date().toISOString()
|
|
63
|
+
}, `${body.filename} ${body.metadata?.category || ''} ${body.metadata?.tags?.join(' ') || ''}`);
|
|
64
|
+
|
|
65
|
+
// Notify user about the upload
|
|
66
|
+
const user = await User.get();
|
|
67
|
+
if (user) {
|
|
68
|
+
await user.send([{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: `📁 New file uploaded: ${body.filename}`
|
|
71
|
+
}]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
fileId: fileId,
|
|
77
|
+
recordId: fileRecord.id,
|
|
78
|
+
filename: body.filename,
|
|
79
|
+
size: file.size,
|
|
80
|
+
message: 'File uploaded and stored successfully'
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export default fileUploadWebhook;
|
|
86
|
+
|