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.
@@ -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
  }
@@ -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.0'; // Default fallback version
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
- API_URL: "https://api.example.com"
76
- API_KEY: "your-api-key-here"
77
- DEBUG_MODE: "false"
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);
@@ -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
  */
@@ -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.2",
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');
@@ -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": "file:..",
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.0",
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",
@@ -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": "file:..",
22
+ "lua-cli": "1.3.2-alpha.2",
23
+ "openai": "^5.23.0",
22
24
  "zod": "^4.1.9"
23
25
  },
24
26
  "devDependencies": {
@@ -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([new GetWeatherTool(), new GetUserDataTool(), new CreateUserDataTool(), new UpdateUserDataTool(), new CreatePostTool()]);
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, env } from "lua-cli/skill";
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
- weatherService: GetWeatherService;
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
- constructor() {
23
- this.weatherService = new GetWeatherService();
24
- }
33
+ const { latitude, longitude, name } = geoData.results[0];
25
34
 
26
- async execute(input: z.infer<typeof this.inputSchema>) {
27
- // Access environment variables safely using the env() function
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}&current_weather=true`;
31
37
 
32
- console.error('Environment variables:', { baseUrl, apiKey, agentId });
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 this.weatherService.getWeather(input.city);
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
+ }