diff --git a/README.md b/README.md index 4188467e..0ae72c19 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ Welcome to evo.ninja. Evo.ninja is an AI agent that builds itself! -It executes operations to achieve a goal. -It is capable of using fuzzy search to find and execute any operation in its library. -Operations are namespaced JavaScript functions with typed arguments and a description. -If it can not find an operation, it will write one itself. +It executes scripts to achieve a goal. +It is capable of using fuzzy search to find and execute any script in its library. +Scripts are namespaced JavaScript functions with typed arguments and a description. +If it can not find a script, it will write one itself. [Roadmap](./ROADMAP.md) diff --git a/ROADMAP.md b/ROADMAP.md index c5d2c55a..fa556b2d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,8 @@ # Roadmap ## High-Pri +x rename operations -> scripts +- add metadata to header of script - chat log, used for showing examples - example https://github.com/polywrap/polygpt#examples @@ -13,8 +15,8 @@ ## Improvements for hackathon - Shims - axios (done?) -- When executeOperation returns undefined it should be an error to the LLM (maybe) (void funcs to return bool?) +- When executeScript returns undefined it should be an error to the LLM (maybe) (void funcs to return bool?) - Function to trim text("error..."). - This should also fix the issue with always displaying '...' even when text is shorter than max length - Better global variable implementation - - Issue is that agent tries to access props of the variable (which is not supported) or use it in operation code, etc + - Issue is that agent tries to access props of the variable (which is not supported) or use it in script code, etc diff --git a/operations/crypto.getPrice.json b/operations/crypto.getPrice.json deleted file mode 100644 index 428abb5e..00000000 --- a/operations/crypto.getPrice.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "crypto.getPrice", - "description": "Get the current price of a specified cryptocurrency.", - "arguments": "{ currency: string }", - "code": "const axios = require('axios');\n\nconst url = 'https://api.coingecko.com/api/v3/simple/price';\nconst params = {\n ids: currency,\n vs_currencies: 'usd'\n};\n\ntry {\n const response = await axios.get(url, { params });\n return response.data[currency].usd;\n} catch (error) {\n throw new Error(`Could not fetch price for ${currency}: ${error.message}`);\n}" -} \ No newline at end of file diff --git a/operations/math.divide.json b/operations/math.divide.json deleted file mode 100644 index 9f759160..00000000 --- a/operations/math.divide.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "math.divide", - "description": "This operation divides two numbers and returns the result.", - "arguments": "{ numerator: number, denominator: number }", - "code": "if (denominator === 0) {\n throw new Error('Cannot divide by zero');\n}\nreturn numerator / denominator;" -} \ No newline at end of file diff --git a/scripts/agent.ask.js b/scripts/agent.ask.js new file mode 100644 index 00000000..6c8b7a76 --- /dev/null +++ b/scripts/agent.ask.js @@ -0,0 +1,5 @@ +return __wrap_subinvoke( + 'plugin/agent', + 'ask', + { message: message } +).value diff --git a/operations/agent.ask.json b/scripts/agent.ask.json similarity index 62% rename from operations/agent.ask.json rename to scripts/agent.ask.json index a4640400..41e8a3db 100644 --- a/operations/agent.ask.json +++ b/scripts/agent.ask.json @@ -2,5 +2,5 @@ "name":"agent.ask", "description":"Sends a message to the user and waits for user response.", "arguments":"{ message: string }", - "code":"return __wrap_subinvoke('plugin/agent', 'ask', { message: message }).value" + "code":"./agent.ask.js" } \ No newline at end of file diff --git a/scripts/agent.onGoalAchieved.js b/scripts/agent.onGoalAchieved.js new file mode 100644 index 00000000..9678144b --- /dev/null +++ b/scripts/agent.onGoalAchieved.js @@ -0,0 +1,5 @@ +return __wrap_subinvoke( + 'plugin/agent', + 'onGoalAchieved', + { } +).value diff --git a/operations/agent.onGoalAchieved.json b/scripts/agent.onGoalAchieved.json similarity index 60% rename from operations/agent.onGoalAchieved.json rename to scripts/agent.onGoalAchieved.json index ba344fe0..3c39e54b 100644 --- a/operations/agent.onGoalAchieved.json +++ b/scripts/agent.onGoalAchieved.json @@ -2,5 +2,5 @@ "name":"agent.onGoalAchieved", "description":"Informs the user that the goal has been achieved.", "arguments":"None", - "code":"return __wrap_subinvoke('plugin/agent', 'onGoalAchieved', { a: 5 }).value" + "code":"./agent.onGoalAchieved.js" } \ No newline at end of file diff --git a/scripts/agent.speak.js b/scripts/agent.speak.js new file mode 100644 index 00000000..fde4fcf1 --- /dev/null +++ b/scripts/agent.speak.js @@ -0,0 +1,5 @@ +return __wrap_subinvoke( + 'plugin/agent', + 'speak', + { message: message } +).value diff --git a/operations/agent.speak.json b/scripts/agent.speak.json similarity index 58% rename from operations/agent.speak.json rename to scripts/agent.speak.json index ed7f2f46..9b3a9a2d 100644 --- a/operations/agent.speak.json +++ b/scripts/agent.speak.json @@ -2,5 +2,5 @@ "name":"agent.speak", "description":"Informs the user by sending a message.", "arguments":"{ message: string }", - "code":"return __wrap_subinvoke('plugin/agent', 'speak', { message: message }).value" + "code":"./agent.speak.js" } \ No newline at end of file diff --git a/scripts/crypto.getPrice.js b/scripts/crypto.getPrice.js new file mode 100644 index 00000000..9b41e246 --- /dev/null +++ b/scripts/crypto.getPrice.js @@ -0,0 +1,14 @@ +const axios = require('axios'); + +const url = 'https://api.coingecko.com/api/v3/simple/price'; +const params = { + ids: currency, + vs_currencies: 'usd' +}; + +try { + const response = await axios.get(url, { params }); + return response.data[currency].usd; +} catch (error) { + throw new Error(`Could not fetch price for ${currency}: ${error.message}`); +} diff --git a/scripts/crypto.getPrice.json b/scripts/crypto.getPrice.json new file mode 100644 index 00000000..17d2ec44 --- /dev/null +++ b/scripts/crypto.getPrice.json @@ -0,0 +1,6 @@ +{ + "name": "crypto.getPrice", + "description": "Get the current price of a specified cryptocurrency.", + "arguments": "{ currency: string }", + "code": "./crypto.getPrice.js" +} \ No newline at end of file diff --git a/operations/fs.readFile.js b/scripts/fs.readFile.js similarity index 100% rename from operations/fs.readFile.js rename to scripts/fs.readFile.js diff --git a/operations/fs.readFile.json b/scripts/fs.readFile.json similarity index 100% rename from operations/fs.readFile.json rename to scripts/fs.readFile.json diff --git a/scripts/fs.writeFile.js b/scripts/fs.writeFile.js new file mode 100644 index 00000000..690025db --- /dev/null +++ b/scripts/fs.writeFile.js @@ -0,0 +1,6 @@ +const fs = require('fs'); +try { + fs.writeFileSync(path, data, encoding); +} catch (error) { + throw new Error(`Failed to write file: ${error.message}`); +} \ No newline at end of file diff --git a/operations/fs.writeFile.json b/scripts/fs.writeFile.json similarity index 51% rename from operations/fs.writeFile.json rename to scripts/fs.writeFile.json index ee8685f9..42d7ed6f 100644 --- a/operations/fs.writeFile.json +++ b/scripts/fs.writeFile.json @@ -2,5 +2,5 @@ "name": "fs.writeFile", "description": "Writes data to a file, replacing the file if it already exists.", "arguments": "{ path: string, data: string, encoding: string }", - "code": "const fs = require('fs');\ntry {\n fs.writeFileSync(path, data, encoding);\n} catch (error) {\n throw new Error(`Failed to write file: ${error.message}`);\n}" + "code": "./fs.writeFile.js" } \ No newline at end of file diff --git a/scripts/math.divide.js b/scripts/math.divide.js new file mode 100644 index 00000000..ba53a4db --- /dev/null +++ b/scripts/math.divide.js @@ -0,0 +1,4 @@ +if (denominator === 0) { + throw new Error('Cannot divide by zero'); +} +return numerator / denominator; \ No newline at end of file diff --git a/scripts/math.divide.json b/scripts/math.divide.json new file mode 100644 index 00000000..5726dfa3 --- /dev/null +++ b/scripts/math.divide.json @@ -0,0 +1,6 @@ +{ + "name": "math.divide", + "description": "This script divides two numbers and returns the result.", + "arguments": "{ numerator: number, denominator: number }", + "code": "./math.divide.js" +} \ No newline at end of file diff --git a/shim-test.js b/shim-test.js index 85c4591e..3d08057c 100644 --- a/shim-test.js +++ b/shim-test.js @@ -86,9 +86,9 @@ function require(lib) { } const __temp = (async function () { - // OPERATION CODE HERE (do not forget to declare arguments of the operation function as local vars) + // SCRIPT CODE HERE (do not forget to declare arguments of the script function as local vars) - //END OPERATION CODE + //END SCRIPT CODE })().then(result => { __wrap_subinvoke("plugin/result", "post", { result: result != null ? result : "undefined" }) }, error => { diff --git a/site/README.md b/site/README.md deleted file mode 100644 index b87cb004..00000000 --- a/site/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Getting Started with Create React App - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.\ -You will also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/src/__tests__/agent.spec.ts b/src/__tests__/agent.spec.ts index 6790c4cb..a1c509bd 100644 --- a/src/__tests__/agent.spec.ts +++ b/src/__tests__/agent.spec.ts @@ -17,7 +17,7 @@ describe('AI Agent Test Suite', () => { // You can add more complex tests here... // For example: - test('Complex Operation: (590 * 204) + (1000 / 2) - 42', () => { + test('Complex script: (590 * 204) + (1000 / 2) - 42', () => { const goal = '(590 * 204) + (1000 / 2) - 42'; const startTime = new Date().getTime(); const result = execSync(`yarn start '${goal}'`, { timeout: oneMinute, encoding: 'utf-8' }); diff --git a/src/__tests__/operations.spec.ts b/src/__tests__/operations.spec.ts deleted file mode 100644 index 8b46be48..00000000 --- a/src/__tests__/operations.spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* -TODO: - - add tests for individual operations -*/ - - diff --git a/src/__tests__/scripts.spec.ts b/src/__tests__/scripts.spec.ts new file mode 100644 index 00000000..251e917a --- /dev/null +++ b/src/__tests__/scripts.spec.ts @@ -0,0 +1,6 @@ +/* +TODO: + - add tests for individual scripts +*/ + + diff --git a/src/evo/functions/createOperation.ts b/src/evo/functions/createScript.ts similarity index 69% rename from src/evo/functions/createOperation.ts rename to src/evo/functions/createScript.ts index 163434f6..b4f16c6a 100644 --- a/src/evo/functions/createOperation.ts +++ b/src/evo/functions/createScript.ts @@ -1,32 +1,32 @@ import { AgentFunction } from "../../functions"; import { WrapClient } from "../../wrap"; -import { addOperation } from "../../operations"; +import { addScript } from "../../scripts"; import { InMemoryWorkspace } from "../../workspaces"; import { Agent as CodeWriterAgent } from "../../code-writer"; import chalk from "chalk"; -export const createOperation: AgentFunction = { +export const createScript: AgentFunction = { definition: { - name: "createOperation", - description: `Create an operation using JavaScript.`, + name: "createScript", + description: `Create a script using JavaScript.`, parameters: { type: "object", properties: { namespace: { type: "string", - description: "The namespace of the operation, e.g. fs.readFile" + description: "The namespace of the script, e.g. fs.readFile" }, description: { type: "string", - description: "The detailed description of the operation." + description: "The detailed description of the script." }, arguments: { type: "string", - description: "The arguments of the operation. E.g. '{ path: string, encoding: string }'. Use only what you need, no optional arguments." + description: "The arguments of the script. E.g. '{ path: string, encoding: string }'. Use only what you need, no optional arguments." }, developerNote: { type: "string", - description: "A note for the developer of the operation, if any." + description: "A note for the developer of the script, if any." } }, required: ["namespace", "description", "arguments"], @@ -41,14 +41,14 @@ export const createOperation: AgentFunction = { if (options.namespace.startsWith("agent.")) { return { ok: false, - result: `Cannot create an operation with namespace ${options.namespace}. Try searching for operations in that namespace instead.`, + result: `Cannot create an script with namespace ${options.namespace}. Try searching for script in that namespace instead.`, } } - + const workspace = new InMemoryWorkspace(); const writer = new CodeWriterAgent(workspace); - console.log(chalk.yellow(`Creating operation '${options.namespace}'...`)); - + console.log(chalk.yellow(`Creating script '${options.namespace}'...`)); + let iterator = writer.run(options.namespace, options.description, options.arguments, options.developerNote); while(true) { @@ -69,15 +69,15 @@ export const createOperation: AgentFunction = { arguments: options.arguments, code: index }; - addOperation(options.namespace, op); - + addScript(options.namespace, op); + const candidates = [ op ]; - + return { ok: true, - result: `Created the following operations:` + + result: `Created the following scripts:` + `\n--------------\n` + `${candidates.map((c) => `Namespace: ${c.name}\nArguments: ${c.arguments}\nDescription: ${c.description}`).join("\n--------------\n")}` + `\n--------------\n`, diff --git a/src/evo/functions/executeOperation.ts b/src/evo/functions/executeScript.ts similarity index 75% rename from src/evo/functions/executeOperation.ts rename to src/evo/functions/executeScript.ts index 7c6ea260..be685a5a 100644 --- a/src/evo/functions/executeOperation.ts +++ b/src/evo/functions/executeScript.ts @@ -1,25 +1,25 @@ import { AgentFunction, WrapClient, functionCodeWrapper, nodeShims } from "../.."; import { JS_ENGINE_URI } from "../../constants"; -import { getOperationByName } from "../../operations"; +import { getScriptByName } from "../../scripts"; -export const executeOperation: AgentFunction = { +export const executeScript: AgentFunction = { definition: { - name: "executeOperation", - description: `Execute an operation.`, + name: "executeScript", + description: `Execute an script.`, parameters: { type: "object", properties: { namespace: { type: "string", - description: "Namespace of the operation to execute" + description: "Namespace of the script to execute" }, arguments: { type: "string", - description: "The arguments to pass into the operation being executed.", + description: "The arguments to pass into the script being executed", }, result: { type: "string", - description: "The name of the variable to store the result of the operation" + description: "The name of the variable to store the result of the script" } }, required: ["name", "arguments", "result"], @@ -35,16 +35,16 @@ export const executeOperation: AgentFunction = { // if (!options.arguments) { // return { // ok: false, - // error: `No arguments provided for operation ${options.name}.`, + // error: `No arguments provided for script ${options.name}.`, // }; // } - const operation = getOperationByName(options.namespace); + const script = getScriptByName(options.namespace); - if (!operation) { + if (!script) { return { ok: false, - error: `Operation ${options.namespace} not found.`, + error: `Script ${options.namespace} not found.`, }; } @@ -70,12 +70,12 @@ export const executeOperation: AgentFunction = { } catch { return { ok: false, - error: `Invalid arguments provided for operation ${options.namespace}: '${options.arguments}' is not valid JSON!`, + error: `Invalid arguments provided for script ${options.namespace}: '${options.arguments}' is not valid JSON!`, }; } const invokeArgs = { - src: nodeShims + functionCodeWrapper(operation.code), + src: nodeShims + functionCodeWrapper(script.code), globals: Object.keys(args).map((key) => ({ name: key, value: JSON.stringify(args[key]), @@ -89,7 +89,7 @@ export const executeOperation: AgentFunction = { }); console.log("----------------"); - console.log("Execute operation output", client.jsPromiseOutput); + console.log("Execute script output", client.jsPromiseOutput); console.log("----------------"); if (result.ok && client.jsPromiseOutput.result) { @@ -105,11 +105,11 @@ export const executeOperation: AgentFunction = { } : { ok: false, - error: "No result returned from operation.", + error: "No result returned from script.", } : { ok: false, - error: result.value.error + "\nCode: " + operation.code, + error: result.value.error + "\nCode: " + script.code, } : { ok: false, diff --git a/src/evo/functions/findOperation.ts b/src/evo/functions/findScript.ts similarity index 60% rename from src/evo/functions/findOperation.ts rename to src/evo/functions/findScript.ts index a60e8eec..c911bea5 100644 --- a/src/evo/functions/findOperation.ts +++ b/src/evo/functions/findScript.ts @@ -1,19 +1,19 @@ -import { AgentFunction, WrapClient, searchOperations } from "../.."; +import { AgentFunction, WrapClient, searchScripts } from "../.."; -export const findOperation: AgentFunction = { +export const findScript: AgentFunction = { definition: { - name: "findOperation", - description: `Search for an operation.`, + name: "findScript", + description: `Search for an script.`, parameters: { type: "object", properties: { namespace: { type: "string", - description: "Partial namespace of the operation" + description: "Partial namespace of the script" }, description: { type: "string", - description: "The detailed description of the arguments and output of the operation." + description: "The detailed description of the arguments and output of the script." }, }, required: ["namespace", "description"], @@ -25,17 +25,17 @@ export const findOperation: AgentFunction = { client: WrapClient ) => { return async (options: { namespace: string, description: string }) => { - const candidates = searchOperations(`${options.namespace} ${options.description}`).slice(0, 5); + const candidates = searchScripts(`${options.namespace} ${options.description}`).slice(0, 5); if (candidates.length === 0) { return { ok: true, - result: `Found no candidates for operation ${options.namespace}. Try creating the operation instead.`, + result: `Found no candidates for script ${options.namespace}. Try creating the script instead.`, }; } return { ok: true, - result: `Found the following candidates for operation: ${options.namespace}:` + + result: `Found the following candidates for script: ${options.namespace}:` + `\n--------------\n` + `${candidates.map((c) => `Namespace: ${c.name}\nArguments: ${c.arguments}\nDescription: ${c.description}`).join("\n--------------\n")}` + `\n--------------\n`, diff --git a/src/evo/functions/index.ts b/src/evo/functions/index.ts index 8b524be8..6310e8e3 100644 --- a/src/evo/functions/index.ts +++ b/src/evo/functions/index.ts @@ -1,13 +1,12 @@ import { AgentFunction } from "../../functions"; -import { createOperation } from "./createOperation"; -import { executeOperation } from "./executeOperation"; -import { findOperation } from "./findOperation"; +import { createScript } from "./createScript"; +import { executeScript } from "./executeScript"; +import { findScript } from "./findScript"; import { readVar } from "./readVar"; export const functions: AgentFunction[] = [ - createOperation, - executeOperation, - findOperation, + createScript, + executeScript, + findScript, readVar, ]; - diff --git a/src/evo/prompts.ts b/src/evo/prompts.ts index 329569d3..b8bd59b6 100644 --- a/src/evo/prompts.ts +++ b/src/evo/prompts.ts @@ -1,11 +1,11 @@ -export const INITIAL_PROMP = `You are an agent that executes operations to accomplish goals.\n` + -`Use the executeOperation function to execute an operation. You can search for new operations using the findOperation function.\n` + -`If you can not find an operation that matches your needs you can create one with the createOperation function.\n` + -`When executing operations use named arguments with TypeScript syntax.\n` + -`When creating operations be sure to declare all the require statements. The code of the operations should be written as if in a function.\n` + +export const INITIAL_PROMP = `You are an agent that executes scripts to accomplish goals.\n` + +`Use the executeScript function to execute a script. You can search for new scripts using the findScript function.\n` + +`If you can not find a script that matches your needs you can create one with the createScript function.\n` + +`When executing scripts use named arguments with TypeScript syntax.\n` + +`When creating scripts be sure to declare all the require statements. The code of the scripts should be written as if in a function.\n` + `Do not use async/await or promises.\n` + -`Use the speak operation to inform the user.\n` + -`Once you have achieved the goal, execute the onGoalAchieved operation in the agent namespace.`; +`Use the speak script to inform the user.\n` + +`Once you have achieved the goal, execute the onGoalAchieved script in the agent namespace.`; export const GOAL_PROMPT = (goal: string) => `The user has the following goal: ${goal}.` -export const LOOP_PREVENTION_PROMPT = "Assistant, are you trying to inform the user? If so, Try calling findOperation with the agent namespace."; \ No newline at end of file +export const LOOP_PREVENTION_PROMPT = "Assistant, are you trying to inform the user? If so, Try calling findScript with the agent namespace."; diff --git a/src/functions/executeFunc.ts b/src/functions/executeFunc.ts index 915046d4..f63d8cf3 100644 --- a/src/functions/executeFunc.ts +++ b/src/functions/executeFunc.ts @@ -26,7 +26,7 @@ export const executeFunc: ExecuteFunc = async ( const func = functions.find((f) => f.definition.name === name); if (!func) { - return ResultErr(`Function ${name} does not exist. Try calling executeOperation instead`); + return ResultErr(`Function ${name} does not exist. Try calling executeScript instead`); } const executor = func.buildExecutor(globals, client, workspace); @@ -51,7 +51,7 @@ export const executeFunc: ExecuteFunc = async ( return ResultErr(`The function '${name}' failed, this is the error:\n----------\n${response.error && response.error.slice ? response.error.slice(0, 300) + "...": "Unknown error."}\nJSON Arguments: ${args}\n----------\\n`); } // const resultStr = JSON.stringify(response.result, null, 2); - if (name === "executeOperation" || name === "eval") { + if (name === "executeScript" || name === "eval") { functionCallSummary += `Result stored into global var: \`{{${fnArgs.result}}}\`. Preview: \`${ response.result ? response.result.slice(0, 200) + "..." diff --git a/src/index.ts b/src/index.ts index af88d193..1dce13e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ export * from './evo'; export * from './functions'; export * from './llm'; export * from './openai'; -export * from './operations'; +export * from './scripts'; export * from './workspaces'; export * from './wrap'; export * from './boilerplate'; diff --git a/src/operations.ts b/src/operations.ts deleted file mode 100644 index 3b2ca279..00000000 --- a/src/operations.ts +++ /dev/null @@ -1,57 +0,0 @@ -import fs from "fs"; -import path from "path"; -import Fuse from "fuse.js"; - -export interface Operation { - name: string; - description: string; - arguments: string; - code: string; -} - -export function searchOperations(query: string): Operation[] { - const operations = getAllOperations(); - - const options = { - keys: [ - "name", - "description" - ] - }; - - const fuse = new Fuse(Object.values(operations), options); - - return fuse.search(query).map((r) => r.item); -} - -export function getAllOperations(): Operation[] { - const ops: Operation[] = []; - fs.readdirSync("./operations") - .filter(file => path.extname(file) === ".json") - .forEach((file) => { - const operation = JSON.parse(fs.readFileSync(`./operations/${file}`, "utf8")); - - // If "code" is a path - if (operation.code.startsWith("./")) { - // Read it from disk - operation.code = fs.readFileSync( - path.join("./operations", operation.code), - "utf-8" - ); - } - - ops.push(operation); - }); - - return ops; -} - -export function getOperationByName(name: string): Operation | undefined { - return getAllOperations().find((op) => op.name === name); -} - -export function addOperation(name: string, operation: Operation) { - fs.writeFileSync(`./operations/${name}.js`, operation.code); - operation.code = `./${name}.js`; - fs.writeFileSync(`./operations/${name}.json`, JSON.stringify(operation, null, 2)); -} diff --git a/src/scripts.ts b/src/scripts.ts new file mode 100644 index 00000000..15f09c8b --- /dev/null +++ b/src/scripts.ts @@ -0,0 +1,57 @@ +import fs from "fs"; +import path from "path"; +import Fuse from "fuse.js"; + +export interface Script { + name: string; + description: string; + arguments: string; + code: string; +} + +export function searchScripts(query: string): Script[] { + const scripts = getAllScripts(); + + const options = { + keys: [ + "name", + "description" + ] + }; + + const fuse = new Fuse(Object.values(scripts), options); + + return fuse.search(query).map((r) => r.item); +} + +export function getAllScripts(): Script[] { + const ops: Script[] = []; + fs.readdirSync("./scripts") + .filter(file => path.extname(file) === ".json") + .forEach((file) => { + const script = JSON.parse(fs.readFileSync(`./scripts/${file}`, "utf8")); + + // If "code" is a path + if (script.code.startsWith("./")) { + // Read it from disk + script.code = fs.readFileSync( + path.join("./scripts", script.code), + "utf-8" + ); + } + + ops.push(script); + }); + + return ops; +} + +export function getScriptByName(name: string): Script | undefined { + return getAllScripts().find((op) => op.name === name); +} + +export function addScript(name: string, script: Script) { + fs.writeFileSync(`./scripts/${name}.js`, script.code); + script.code = `./${name}.js`; + fs.writeFileSync(`./scripts/${name}.json`, JSON.stringify(script, null, 2)); +}