lua-cli 1.3.2-alpha.2 → 1.3.2-alpha.3
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/commands/compile.js +569 -974
- package/dist/commands/init.js +10 -0
- package/dist/utils/files.js +4 -6
- package/dist/utils/sandbox.d.ts +1 -70
- package/dist/utils/sandbox.js +113 -3
- package/package.json +2 -1
- package/template/create-test.cjs +39 -0
- package/template/lua.skill.yaml +4 -3
- package/template/package-lock.json +34 -2
- package/template/package.json +3 -1
- package/template/src/index.ts +13 -2
- package/template/src/seed.ts +46 -0
- package/template/src/tools/GetWeatherTool.ts +32 -15
- package/template/src/tools/PaymentTool.ts +52 -0
- package/template/src/tools/SearchProducts.ts +43 -0
package/dist/commands/init.js
CHANGED
|
@@ -300,6 +300,16 @@ export async function initCommand() {
|
|
|
300
300
|
createSkillYaml(selectedAgent.agentId, selectedOrg.id, actualSkillName, skillData?.id, persona, welcomeMessage);
|
|
301
301
|
writeProgress("✅ Created lua.skill.yaml");
|
|
302
302
|
writeProgress("✅ Copied template files");
|
|
303
|
+
// Install dependencies
|
|
304
|
+
writeProgress("📦 Installing dependencies...");
|
|
305
|
+
const { execSync } = await import('child_process');
|
|
306
|
+
try {
|
|
307
|
+
execSync('npm install', { stdio: 'inherit', cwd: currentDir });
|
|
308
|
+
writeProgress("✅ Dependencies installed successfully");
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
writeProgress("⚠️ Warning: Failed to install dependencies automatically. Please run 'npm install' manually.");
|
|
312
|
+
}
|
|
303
313
|
writeSuccess("✅ Lua skill project initialized successfully!");
|
|
304
314
|
}, "initialization");
|
|
305
315
|
}
|
package/dist/utils/files.js
CHANGED
|
@@ -29,7 +29,7 @@ function updatePackageJson(srcPath, destPath) {
|
|
|
29
29
|
const templatePackageJson = JSON.parse(fs.readFileSync(srcPath, 'utf8'));
|
|
30
30
|
// Get the current CLI version from the CLI's own package.json
|
|
31
31
|
// We need to find the CLI's package.json, not the current working directory's
|
|
32
|
-
let currentCliVersion = '1.3.2-alpha.
|
|
32
|
+
let currentCliVersion = '1.3.2-alpha.2'; // Default fallback version
|
|
33
33
|
try {
|
|
34
34
|
// Try to find the CLI's package.json by looking for it in common locations
|
|
35
35
|
const possiblePaths = [
|
|
@@ -72,11 +72,9 @@ skill:
|
|
|
72
72
|
version: "0.0.1"
|
|
73
73
|
${skillIdSection} env:
|
|
74
74
|
# Example environment variables - customize these for your skill
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
MAX_RETRIES: "3"
|
|
79
|
-
TIMEOUT_MS: "5000"
|
|
75
|
+
PINECONE_API_KEY: "pcsk_5iFtQD_xxxxx"
|
|
76
|
+
OPENAI_API_KEY: "sk-proj--xxxx"
|
|
77
|
+
STRIPE_SECRET_KEY: "sk_test_xxxx"
|
|
80
78
|
# Add your own environment variables below
|
|
81
79
|
# CUSTOM_VAR: "custom-value"`;
|
|
82
80
|
fs.writeFileSync("lua.skill.yaml", yamlContent);
|
package/dist/utils/sandbox.d.ts
CHANGED
|
@@ -11,76 +11,7 @@ export interface ExecuteToolOptions extends SandboxOptions {
|
|
|
11
11
|
/**
|
|
12
12
|
* Creates a VM sandbox context with all necessary globals and utilities
|
|
13
13
|
*/
|
|
14
|
-
export declare function createSandbox(options: SandboxOptions):
|
|
15
|
-
require: NodeJS.Require;
|
|
16
|
-
console: any;
|
|
17
|
-
Buffer: BufferConstructor;
|
|
18
|
-
setTimeout: typeof setTimeout;
|
|
19
|
-
setInterval: typeof setInterval;
|
|
20
|
-
clearTimeout: typeof clearTimeout;
|
|
21
|
-
clearInterval: typeof clearInterval;
|
|
22
|
-
process: NodeJS.Process;
|
|
23
|
-
global: typeof globalThis;
|
|
24
|
-
__dirname: string;
|
|
25
|
-
__filename: string;
|
|
26
|
-
module: {
|
|
27
|
-
exports: {};
|
|
28
|
-
};
|
|
29
|
-
exports: {};
|
|
30
|
-
fetch: typeof fetch;
|
|
31
|
-
URLSearchParams: {
|
|
32
|
-
new (init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams;
|
|
33
|
-
prototype: URLSearchParams;
|
|
34
|
-
};
|
|
35
|
-
URL: {
|
|
36
|
-
new (url: string | URL, base?: string | URL): URL;
|
|
37
|
-
prototype: URL;
|
|
38
|
-
canParse(url: string | URL, base?: string | URL): boolean;
|
|
39
|
-
createObjectURL(obj: Blob | MediaSource): string;
|
|
40
|
-
parse(url: string | URL, base?: string | URL): URL | null;
|
|
41
|
-
revokeObjectURL(url: string): void;
|
|
42
|
-
};
|
|
43
|
-
Headers: {
|
|
44
|
-
new (init?: HeadersInit): Headers;
|
|
45
|
-
prototype: Headers;
|
|
46
|
-
};
|
|
47
|
-
Request: {
|
|
48
|
-
new (input: RequestInfo | URL, init?: RequestInit): Request;
|
|
49
|
-
prototype: Request;
|
|
50
|
-
};
|
|
51
|
-
Response: {
|
|
52
|
-
new (body?: BodyInit | null, init?: ResponseInit): Response;
|
|
53
|
-
prototype: Response;
|
|
54
|
-
error(): Response;
|
|
55
|
-
json(data: any, init?: ResponseInit): Response;
|
|
56
|
-
redirect(url: string | URL, status?: number): Response;
|
|
57
|
-
};
|
|
58
|
-
Object: ObjectConstructor;
|
|
59
|
-
Array: ArrayConstructor;
|
|
60
|
-
String: StringConstructor;
|
|
61
|
-
Number: NumberConstructor;
|
|
62
|
-
Boolean: BooleanConstructor;
|
|
63
|
-
Date: DateConstructor;
|
|
64
|
-
Math: Math;
|
|
65
|
-
JSON: JSON;
|
|
66
|
-
Error: ErrorConstructor;
|
|
67
|
-
TypeError: TypeErrorConstructor;
|
|
68
|
-
ReferenceError: ReferenceErrorConstructor;
|
|
69
|
-
SyntaxError: SyntaxErrorConstructor;
|
|
70
|
-
globalThis: typeof globalThis;
|
|
71
|
-
undefined: undefined;
|
|
72
|
-
null: null;
|
|
73
|
-
Infinity: number;
|
|
74
|
-
NaN: number;
|
|
75
|
-
user: {
|
|
76
|
-
data: {
|
|
77
|
-
update: (data: any) => Promise<import("../services/api.js").ApiResponse<any>>;
|
|
78
|
-
get: () => Promise<any>;
|
|
79
|
-
create: (data: any) => Promise<any>;
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
env: (key: string) => string;
|
|
83
|
-
};
|
|
14
|
+
export declare function createSandbox(options: SandboxOptions): any;
|
|
84
15
|
/**
|
|
85
16
|
* Executes a tool in a VM sandbox
|
|
86
17
|
*/
|
package/dist/utils/sandbox.js
CHANGED
|
@@ -8,9 +8,16 @@ import { readSkillConfig } from "./files.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export function createSandbox(options) {
|
|
10
10
|
const { apiKey, agentId, customConsole, broadcastLog } = options;
|
|
11
|
-
// Extract environment variables from YAML config
|
|
11
|
+
// Extract environment variables from YAML config and merge with process.env
|
|
12
12
|
const config = readSkillConfig();
|
|
13
13
|
const envVars = {};
|
|
14
|
+
// Copy process.env, filtering out undefined values
|
|
15
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
16
|
+
if (value !== undefined) {
|
|
17
|
+
envVars[key] = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Override with config values
|
|
14
21
|
if (config?.skill?.env) {
|
|
15
22
|
for (const [key, value] of Object.entries(config.skill.env)) {
|
|
16
23
|
envVars[key] = value;
|
|
@@ -29,6 +36,85 @@ export function createSandbox(options) {
|
|
|
29
36
|
};
|
|
30
37
|
// Create console object (use custom console if provided, otherwise default)
|
|
31
38
|
const consoleObj = customConsole || console;
|
|
39
|
+
// Create comprehensive polyfills for browser/Node.js APIs
|
|
40
|
+
const polyfills = {
|
|
41
|
+
// AbortController polyfill
|
|
42
|
+
AbortController: globalThis.AbortController || class AbortController {
|
|
43
|
+
constructor() {
|
|
44
|
+
this.signal = {
|
|
45
|
+
aborted: false,
|
|
46
|
+
addEventListener: () => { },
|
|
47
|
+
removeEventListener: () => { },
|
|
48
|
+
dispatchEvent: () => { }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
abort() {
|
|
52
|
+
this.signal.aborted = true;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
// FormData polyfill
|
|
56
|
+
FormData: globalThis.FormData || class FormData {
|
|
57
|
+
constructor() {
|
|
58
|
+
this._data = [];
|
|
59
|
+
}
|
|
60
|
+
append(name, value) {
|
|
61
|
+
this._data.push({ name, value });
|
|
62
|
+
}
|
|
63
|
+
get(name) {
|
|
64
|
+
const item = this._data.find(d => d.name === name);
|
|
65
|
+
return item ? item.value : null;
|
|
66
|
+
}
|
|
67
|
+
getAll(name) {
|
|
68
|
+
return this._data.filter(d => d.name === name).map(d => d.value);
|
|
69
|
+
}
|
|
70
|
+
has(name) {
|
|
71
|
+
return this._data.some(d => d.name === name);
|
|
72
|
+
}
|
|
73
|
+
delete(name) {
|
|
74
|
+
this._data = this._data.filter(d => d.name !== name);
|
|
75
|
+
}
|
|
76
|
+
set(name, value) {
|
|
77
|
+
this.delete(name);
|
|
78
|
+
this.append(name, value);
|
|
79
|
+
}
|
|
80
|
+
entries() {
|
|
81
|
+
return this._data.map(d => [d.name, d.value]);
|
|
82
|
+
}
|
|
83
|
+
keys() {
|
|
84
|
+
return this._data.map(d => d.name);
|
|
85
|
+
}
|
|
86
|
+
values() {
|
|
87
|
+
return this._data.map(d => d.value);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
// TextEncoder/TextDecoder polyfills
|
|
91
|
+
TextEncoder: globalThis.TextEncoder || class TextEncoder {
|
|
92
|
+
encode(input) {
|
|
93
|
+
return Buffer.from(input, 'utf8');
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
TextDecoder: globalThis.TextDecoder || class TextDecoder {
|
|
97
|
+
decode(input) {
|
|
98
|
+
return Buffer.from(input).toString('utf8');
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
// Crypto polyfill (basic)
|
|
102
|
+
crypto: globalThis.crypto || {
|
|
103
|
+
randomUUID: () => {
|
|
104
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
105
|
+
const r = Math.random() * 16 | 0;
|
|
106
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
107
|
+
return v.toString(16);
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
getRandomValues: (array) => {
|
|
111
|
+
for (let i = 0; i < array.length; i++) {
|
|
112
|
+
array[i] = Math.floor(Math.random() * 256);
|
|
113
|
+
}
|
|
114
|
+
return array;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
32
118
|
// Create a sandbox context
|
|
33
119
|
const sandbox = {
|
|
34
120
|
require,
|
|
@@ -38,19 +124,27 @@ export function createSandbox(options) {
|
|
|
38
124
|
setInterval,
|
|
39
125
|
clearTimeout,
|
|
40
126
|
clearInterval,
|
|
41
|
-
process
|
|
127
|
+
process: {
|
|
128
|
+
...process,
|
|
129
|
+
env: envVars
|
|
130
|
+
},
|
|
42
131
|
global: globalThis,
|
|
43
132
|
__dirname: process.cwd(),
|
|
44
133
|
__filename: path.join(process.cwd(), 'index.ts'),
|
|
45
134
|
module: { exports: {} },
|
|
46
135
|
exports: {},
|
|
47
|
-
// Web APIs
|
|
136
|
+
// Web APIs with polyfills
|
|
48
137
|
fetch: globalThis.fetch,
|
|
49
138
|
URLSearchParams: globalThis.URLSearchParams,
|
|
50
139
|
URL: globalThis.URL,
|
|
51
140
|
Headers: globalThis.Headers,
|
|
52
141
|
Request: globalThis.Request,
|
|
53
142
|
Response: globalThis.Response,
|
|
143
|
+
FormData: polyfills.FormData,
|
|
144
|
+
AbortController: polyfills.AbortController,
|
|
145
|
+
TextEncoder: polyfills.TextEncoder,
|
|
146
|
+
TextDecoder: polyfills.TextDecoder,
|
|
147
|
+
crypto: polyfills.crypto,
|
|
54
148
|
// Additional globals
|
|
55
149
|
Object,
|
|
56
150
|
Array,
|
|
@@ -99,6 +193,22 @@ module.exports = async (input) => {
|
|
|
99
193
|
`;
|
|
100
194
|
// Execute the code in the sandbox
|
|
101
195
|
const context = vm.createContext(sandbox);
|
|
196
|
+
// Add polyfills to global scope for libraries that expect them
|
|
197
|
+
vm.runInContext(`
|
|
198
|
+
if (typeof global !== 'undefined') {
|
|
199
|
+
global.AbortController = AbortController;
|
|
200
|
+
global.FormData = FormData;
|
|
201
|
+
global.TextEncoder = TextEncoder;
|
|
202
|
+
global.TextDecoder = TextDecoder;
|
|
203
|
+
global.crypto = crypto;
|
|
204
|
+
global.fetch = fetch;
|
|
205
|
+
global.Headers = Headers;
|
|
206
|
+
global.Request = Request;
|
|
207
|
+
global.Response = Response;
|
|
208
|
+
global.URL = URL;
|
|
209
|
+
global.URLSearchParams = URLSearchParams;
|
|
210
|
+
}
|
|
211
|
+
`, context);
|
|
102
212
|
vm.runInContext(commonJsWrapper, context);
|
|
103
213
|
// Get the exported function and execute it
|
|
104
214
|
const executeFunction = context.module.exports;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lua-cli",
|
|
3
|
-
"version": "1.3.2-alpha.
|
|
3
|
+
"version": "1.3.2-alpha.3",
|
|
4
4
|
"description": "Command-line interface for Lua AI platform - develop, test, and deploy LuaSkills with custom tools",
|
|
5
5
|
"readmeFilename": "README.md",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"react": "^18.2.0",
|
|
67
67
|
"react-dom": "^18.2.0",
|
|
68
68
|
"socket.io-client": "^4.7.2",
|
|
69
|
+
"ts-morph": "^27.0.0",
|
|
69
70
|
"ws": "^8.18.3",
|
|
70
71
|
"zod": "^4.1.9"
|
|
71
72
|
},
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
// Read the compiled search_movies.js file
|
|
4
|
+
const searchMoviesCode = fs.readFileSync('.lua/search_movies.js', 'utf8');
|
|
5
|
+
|
|
6
|
+
// Create a test function with env function
|
|
7
|
+
const testFunction = `
|
|
8
|
+
// Mock env function
|
|
9
|
+
const env = (key) => {
|
|
10
|
+
const envVars = {
|
|
11
|
+
'PINECONE_API_KEY': 'test-pinecone-key',
|
|
12
|
+
'OPENAI_API_KEY': 'test-openai-key'
|
|
13
|
+
};
|
|
14
|
+
return envVars[key];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
${searchMoviesCode}
|
|
18
|
+
|
|
19
|
+
// Test the function
|
|
20
|
+
(async () => {
|
|
21
|
+
try {
|
|
22
|
+
console.log('Testing SearchMovies tool...');
|
|
23
|
+
const result = await module.exports({ query: 'avatar' });
|
|
24
|
+
console.log('✅ Success:', result);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.log('❌ Error:', error.message);
|
|
27
|
+
if (error.message.includes('FormData is not defined')) {
|
|
28
|
+
console.log('❌ FormData error still exists - recursive bundling not working');
|
|
29
|
+
} else {
|
|
30
|
+
console.log('✅ FormData error resolved - recursive bundling working');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})();
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
// Write test file
|
|
37
|
+
fs.writeFileSync('test-searchmovies.cjs', testFunction);
|
|
38
|
+
|
|
39
|
+
console.log('Test file created. Run: node test-searchmovies.cjs');
|
package/template/lua.skill.yaml
CHANGED
|
@@ -10,7 +10,8 @@ skill:
|
|
|
10
10
|
version: "0.0.1"
|
|
11
11
|
skillId: "0aa3b6fe-45b2-40a9-a19c-b7bf85cbc798"
|
|
12
12
|
env:
|
|
13
|
+
PINECONE_API_KEY: "your-pinecone-api-key-here"
|
|
14
|
+
OPENAI_API_KEY: "your-openai-api-key-here"
|
|
15
|
+
STRIPE_SECRET_KEY: "your-stripe-secret-key-here"
|
|
13
16
|
BASE_URL: "http://localhost:3000"
|
|
14
|
-
API_KEY: "1234567890"
|
|
15
|
-
NAME: "test-3"
|
|
16
|
-
PINECONE_API_KEY: "pcsk_5iFtQD_CzPDnQgjMwhDgVKwJhiYRq53U7agFc6yXiiwwJrTeEWoh1BveSZzvZwA8ZVXoky"
|
|
17
|
+
API_KEY: "1234567890"
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
"version": "1.0.1-beta.0",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"@pinecone-database/pinecone": "^6.1.2",
|
|
12
13
|
"@types/inquirer": "^9.0.9",
|
|
13
14
|
"@types/js-yaml": "^4.0.9",
|
|
14
15
|
"axios": "^1.6.0",
|
|
15
16
|
"inquirer": "^12.9.6",
|
|
16
17
|
"js-yaml": "^4.1.0",
|
|
17
|
-
"lua-cli": "
|
|
18
|
+
"lua-cli": "1.3.2-alpha.2",
|
|
19
|
+
"openai": "^5.23.0",
|
|
18
20
|
"zod": "^4.1.9"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
}
|
|
25
27
|
},
|
|
26
28
|
"..": {
|
|
27
|
-
"version": "1.3.2-alpha.
|
|
29
|
+
"version": "1.3.2-alpha.2",
|
|
28
30
|
"license": "MIT",
|
|
29
31
|
"dependencies": {
|
|
30
32
|
"commander": "^14.0.1",
|
|
@@ -838,6 +840,15 @@
|
|
|
838
840
|
}
|
|
839
841
|
}
|
|
840
842
|
},
|
|
843
|
+
"node_modules/@pinecone-database/pinecone": {
|
|
844
|
+
"version": "6.1.2",
|
|
845
|
+
"resolved": "https://registry.npmjs.org/@pinecone-database/pinecone/-/pinecone-6.1.2.tgz",
|
|
846
|
+
"integrity": "sha512-ydIlbtgIIHFgBL08sPzua5ckmOgtjgDz8xg21CnP1fqnnEgDmOlnfd10MRKU+fvFRhDlh4Md37SwZDr0d4cBqg==",
|
|
847
|
+
"license": "Apache-2.0",
|
|
848
|
+
"engines": {
|
|
849
|
+
"node": ">=18.0.0"
|
|
850
|
+
}
|
|
851
|
+
},
|
|
841
852
|
"node_modules/@types/inquirer": {
|
|
842
853
|
"version": "9.0.9",
|
|
843
854
|
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.9.tgz",
|
|
@@ -1360,6 +1371,27 @@
|
|
|
1360
1371
|
"node": "^18.17.0 || >=20.5.0"
|
|
1361
1372
|
}
|
|
1362
1373
|
},
|
|
1374
|
+
"node_modules/openai": {
|
|
1375
|
+
"version": "5.23.0",
|
|
1376
|
+
"resolved": "https://registry.npmjs.org/openai/-/openai-5.23.0.tgz",
|
|
1377
|
+
"integrity": "sha512-Cfq155NHzI7VWR67LUNJMIgPZy2oSh7Fld/OKhxq648BiUjELAvcge7g30xJ6vAfwwXf6TVK0KKuN+3nmIJG/A==",
|
|
1378
|
+
"license": "Apache-2.0",
|
|
1379
|
+
"bin": {
|
|
1380
|
+
"openai": "bin/cli"
|
|
1381
|
+
},
|
|
1382
|
+
"peerDependencies": {
|
|
1383
|
+
"ws": "^8.18.0",
|
|
1384
|
+
"zod": "^3.23.8"
|
|
1385
|
+
},
|
|
1386
|
+
"peerDependenciesMeta": {
|
|
1387
|
+
"ws": {
|
|
1388
|
+
"optional": true
|
|
1389
|
+
},
|
|
1390
|
+
"zod": {
|
|
1391
|
+
"optional": true
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
},
|
|
1363
1395
|
"node_modules/proxy-from-env": {
|
|
1364
1396
|
"version": "1.1.0",
|
|
1365
1397
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
package/template/package.json
CHANGED
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
+
"@pinecone-database/pinecone": "^6.1.2",
|
|
16
17
|
"@types/inquirer": "^9.0.9",
|
|
17
18
|
"@types/js-yaml": "^4.0.9",
|
|
18
19
|
"axios": "^1.6.0",
|
|
19
20
|
"inquirer": "^12.9.6",
|
|
20
21
|
"js-yaml": "^4.1.0",
|
|
21
|
-
"lua-cli": "
|
|
22
|
+
"lua-cli": "1.3.2-alpha.2",
|
|
23
|
+
"openai": "^5.23.0",
|
|
22
24
|
"zod": "^4.1.9"
|
|
23
25
|
},
|
|
24
26
|
"devDependencies": {
|
package/template/src/index.ts
CHANGED
|
@@ -3,13 +3,23 @@ import { LuaSkill } from "lua-cli/skill";
|
|
|
3
3
|
import GetWeatherTool from "./tools/GetWeatherTool";
|
|
4
4
|
import { GetUserDataTool, CreateUserDataTool, UpdateUserDataTool } from "./tools/UserDataTool";
|
|
5
5
|
import CreatePostTool from "./tools/CreatePostTool";
|
|
6
|
+
import { SearchProductsTool } from "./tools/SearchProducts";
|
|
7
|
+
import CreatePaymentLinkTool from "./tools/PaymentTool";
|
|
6
8
|
|
|
7
9
|
// Initialize skill with tools
|
|
8
10
|
const skill = new LuaSkill({
|
|
9
11
|
description: "A comprehensive Lua skill with weather, user data, post creation, and mathematical tools",
|
|
10
12
|
context: "This skill provides various utilities including weather information, user data retrieval, post creation, basic calculator operations, and advanced mathematical functions. Use get_weather to fetch current weather conditions for any city, get_user_data to retrieve user information, create_post to publish new posts, calculator for basic arithmetic operations, and advanced_math for complex mathematical computations like factorials, prime checking, fibonacci sequences, and greatest common divisor calculations."
|
|
11
13
|
});
|
|
12
|
-
skill.addTools([
|
|
14
|
+
skill.addTools([
|
|
15
|
+
new GetWeatherTool(),
|
|
16
|
+
new GetUserDataTool(),
|
|
17
|
+
new CreateUserDataTool(),
|
|
18
|
+
new UpdateUserDataTool(),
|
|
19
|
+
new CreatePostTool(),
|
|
20
|
+
new SearchProductsTool(),
|
|
21
|
+
new CreatePaymentLinkTool()]
|
|
22
|
+
);
|
|
13
23
|
|
|
14
24
|
// Test cases
|
|
15
25
|
const testCases = [
|
|
@@ -26,8 +36,9 @@ const testCases = [
|
|
|
26
36
|
];
|
|
27
37
|
|
|
28
38
|
async function runTests() {
|
|
39
|
+
// await seedProducts();
|
|
29
40
|
console.log("🧪 Running tool tests...\n");
|
|
30
|
-
|
|
41
|
+
|
|
31
42
|
for (const [index, testCase] of testCases.entries()) {
|
|
32
43
|
try {
|
|
33
44
|
console.log(`Test ${index + 1}: ${testCase.tool}`);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
import OpenAI from "openai";
|
|
3
|
+
import { Pinecone } from "@pinecone-database/pinecone";
|
|
4
|
+
import { env } from "lua-cli/skill";
|
|
5
|
+
|
|
6
|
+
const openai = new OpenAI({ apiKey: env("OPENAI_API_KEY") || "" });
|
|
7
|
+
const pinecone = new Pinecone({ apiKey: env("PINECONE_API_KEY") || "" });
|
|
8
|
+
const indexName = "products-demo";
|
|
9
|
+
|
|
10
|
+
async function embed(text: string) {
|
|
11
|
+
const res = await openai.embeddings.create({
|
|
12
|
+
model: "text-embedding-3-small",
|
|
13
|
+
input: text,
|
|
14
|
+
});
|
|
15
|
+
return res.data[0].embedding;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default async function seedProducts() {
|
|
19
|
+
const index = pinecone.Index(indexName);
|
|
20
|
+
|
|
21
|
+
// Example: Fake Store API
|
|
22
|
+
const response = await fetch("https://fakestoreapi.com/products");
|
|
23
|
+
const products = await response.json() as any[];
|
|
24
|
+
|
|
25
|
+
const vectors = await Promise.all(
|
|
26
|
+
products.map(async (product) => {
|
|
27
|
+
const embedding = await embed(`${product.title}. ${product.description}`);
|
|
28
|
+
return {
|
|
29
|
+
id: product.id.toString(),
|
|
30
|
+
values: embedding,
|
|
31
|
+
metadata: {
|
|
32
|
+
title: product.title,
|
|
33
|
+
description: product.description,
|
|
34
|
+
category: product.category,
|
|
35
|
+
price: product.price,
|
|
36
|
+
image: product.image,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
await index.upsert(vectors);
|
|
43
|
+
console.log("Dummy products inserted!");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
seedProducts().catch(console.error);
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { LuaTool
|
|
1
|
+
import { LuaTool } from "lua-cli/skill";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import GetWeatherService from "../services/GetWeather";
|
|
4
|
-
|
|
5
3
|
|
|
6
4
|
export default class GetWeatherTool implements LuaTool {
|
|
7
5
|
name = "get_weather";
|
|
@@ -17,20 +15,39 @@ export default class GetWeatherTool implements LuaTool {
|
|
|
17
15
|
description: z.string().optional()
|
|
18
16
|
});
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
19
|
+
// Step 1: Geocode city → lat/lon (use Open-Meteo’s free geocoding API)
|
|
20
|
+
const geoUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(
|
|
21
|
+
input.city
|
|
22
|
+
)}&count=1`;
|
|
23
|
+
|
|
24
|
+
const geoRes = await fetch(geoUrl);
|
|
25
|
+
if (!geoRes.ok) {
|
|
26
|
+
throw new Error(`Failed to fetch location: ${geoRes.statusText}`);
|
|
27
|
+
}
|
|
28
|
+
const geoData = await geoRes.json();
|
|
29
|
+
if (!geoData.results || geoData.results.length === 0) {
|
|
30
|
+
throw new Error(`No location found for ${input.city}`);
|
|
31
|
+
}
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
this.weatherService = new GetWeatherService();
|
|
24
|
-
}
|
|
33
|
+
const { latitude, longitude, name } = geoData.results[0];
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const baseUrl = env('BASE_URL');
|
|
29
|
-
const apiKey = env('API_KEY');
|
|
30
|
-
const agentId = env('AGENT_ID');
|
|
35
|
+
// Step 2: Get weather for lat/lon
|
|
36
|
+
const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true`;
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
const weatherRes = await fetch(weatherUrl);
|
|
39
|
+
if (!weatherRes.ok) {
|
|
40
|
+
throw new Error(`Failed to fetch weather: ${weatherRes.statusText}`);
|
|
41
|
+
}
|
|
42
|
+
const weatherData = await weatherRes.json();
|
|
43
|
+
const current = weatherData.current_weather;
|
|
33
44
|
|
|
34
|
-
return
|
|
45
|
+
return {
|
|
46
|
+
weather: current.weathercode?.toString() || "Unknown",
|
|
47
|
+
city: name,
|
|
48
|
+
temperature: current.temperature,
|
|
49
|
+
humidity: undefined, // Open-Meteo current_weather does not include humidity
|
|
50
|
+
description: `Windspeed ${current.windspeed} km/h`
|
|
51
|
+
};
|
|
35
52
|
}
|
|
36
|
-
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { LuaTool, env } from "lua-cli/skill";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export default class CreatePaymentLinkTool implements LuaTool {
|
|
5
|
+
name = "create_payment_link";
|
|
6
|
+
description = "Create a Stripe payment link for a one-time payment";
|
|
7
|
+
inputSchema = z.object({
|
|
8
|
+
amount: z.number().int().positive(), // in cents
|
|
9
|
+
currency: z.string().default("usd"),
|
|
10
|
+
quantity: z.number().int().positive().default(1),
|
|
11
|
+
});
|
|
12
|
+
outputSchema = z.object({
|
|
13
|
+
url: z.string(),
|
|
14
|
+
id: z.string(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
18
|
+
const apiKey = env("STRIPE_SECRET_KEY");
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
throw new Error("STRIPE_SECRET_KEY is not set in environment variables");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Create a one-time Checkout Session with dummy redirect URLs
|
|
24
|
+
const sessionRes = await fetch("https://api.stripe.com/v1/checkout/sessions", {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${apiKey}`,
|
|
28
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
29
|
+
},
|
|
30
|
+
body: new URLSearchParams({
|
|
31
|
+
mode: "payment",
|
|
32
|
+
"line_items[0][price_data][currency]": input.currency,
|
|
33
|
+
"line_items[0][price_data][product_data][name]": "One-time Payment",
|
|
34
|
+
"line_items[0][price_data][unit_amount]": input.amount.toString(),
|
|
35
|
+
"line_items[0][quantity]": input.quantity.toString(),
|
|
36
|
+
success_url: "https://example.com/success",
|
|
37
|
+
cancel_url: "https://example.com/cancel",
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!sessionRes.ok) {
|
|
42
|
+
throw new Error(`Failed to create checkout session: ${await sessionRes.text()}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const session = await sessionRes.json();
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
url: session.url,
|
|
49
|
+
id: session.id,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { env, LuaTool } from "lua-cli/skill";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { user } from 'lua-cli/user-data-api';
|
|
4
|
+
import { Pinecone, Index } from '@pinecone-database/pinecone';
|
|
5
|
+
import OpenAI from "openai";
|
|
6
|
+
export class SearchProductsTool implements LuaTool {
|
|
7
|
+
name = "search_products";
|
|
8
|
+
description = "Search for products";
|
|
9
|
+
inputSchema = z.object({
|
|
10
|
+
query: z.string()
|
|
11
|
+
});
|
|
12
|
+
pinecone: Pinecone;
|
|
13
|
+
openai: OpenAI;
|
|
14
|
+
index: Index;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
const pinecone = new Pinecone({
|
|
18
|
+
apiKey: env('PINECONE_API_KEY') || ''
|
|
19
|
+
});
|
|
20
|
+
this.openai = new OpenAI({ apiKey: env('OPENAI_API_KEY') || '' });
|
|
21
|
+
this.pinecone = pinecone;
|
|
22
|
+
this.index = pinecone.Index('products-demo');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async embedText(text: string) {
|
|
26
|
+
const embeddingResponse = await this.openai.embeddings.create({
|
|
27
|
+
model: "text-embedding-3-small",
|
|
28
|
+
input: text,
|
|
29
|
+
});
|
|
30
|
+
return embeddingResponse.data[0].embedding;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
34
|
+
const queryVector = await this.embedText(input.query);
|
|
35
|
+
const results = await this.index.query({
|
|
36
|
+
vector: queryVector,
|
|
37
|
+
topK: 3,
|
|
38
|
+
includeMetadata: true,
|
|
39
|
+
});
|
|
40
|
+
console.log(results);
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
}
|