From 03c4b6982293c5be31d7eda89b6dcb8c1a7a8059 Mon Sep 17 00:00:00 2001 From: sigoden Date: Sun, 19 May 2024 22:43:49 +0800 Subject: feat: supports functions in bash/js/python/ruby (#6) --- .github/workflows/ci.yaml | 25 ++++- .gitignore | 5 +- Argcfile.sh | 234 +++++++++++++++++++++++++++++++++++++++------- README.md | 149 +++++++++++++++++++++++------ bin/get_current_weather | 11 --- bin/may_execute_command | 11 --- bin/search_duckduckgo | 16 ---- bin/search_wolframalpha | 24 ----- cmd/cmd.js | 36 +++++++ cmd/cmd.py | 43 +++++++++ cmd/cmd.rb | 36 +++++++ js/may_execute_js_code.js | 22 +++++ py/may_execute_py_code.py | 21 +++++ rb/may_execute_rb_code.rb | 22 +++++ sh/get_current_weather.sh | 11 +++ sh/may_execute_command.sh | 11 +++ sh/search_duckduckgo.sh | 16 ++++ sh/search_wolframalpha.sh | 24 +++++ 18 files changed, 584 insertions(+), 133 deletions(-) delete mode 100755 bin/get_current_weather delete mode 100755 bin/may_execute_command delete mode 100755 bin/search_duckduckgo delete mode 100755 bin/search_wolframalpha create mode 100755 cmd/cmd.js create mode 100755 cmd/cmd.py create mode 100755 cmd/cmd.rb create mode 100644 js/may_execute_js_code.js create mode 100644 py/may_execute_py_code.py create mode 100644 rb/may_execute_rb_code.rb create mode 100755 sh/get_current_weather.sh create mode 100755 sh/may_execute_command.sh create mode 100755 sh/search_duckduckgo.sh create mode 100755 sh/search_wolframalpha.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8518a2d..2dbea9f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,10 +16,14 @@ jobs: all: name: All - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: extractions/setup-crate@v1 with: @@ -29,5 +33,18 @@ jobs: - name: Check versions run: argc version - - name: Check scripts - run: argc build \ No newline at end of file + - name: Setup Node.js + uses: actions/setup-node@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + + - name: Run Test + run: argc test \ No newline at end of file diff --git a/.gitignore b/.gitignore index c0e41fc..4dcff7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ /tmp /functions.txt +/functions.txt.test /functions.json +/bin /.env -*.cmd \ No newline at end of file +*.cmd +__pycache__ \ No newline at end of file diff --git a/Argcfile.sh b/Argcfile.sh index 117d719..79c810b 100644 --- a/Argcfile.sh +++ b/Argcfile.sh @@ -3,60 +3,184 @@ set -e # @meta dotenv +BIN_DIR=bin + +LANG_CMDS=( \ + "sh:bash" \ + "js:node" \ + "py:python" \ + "rb:ruby" \ +) + # @cmd Call the function # @arg func![`_choice_func`] The function name # @arg args~[?`_choice_func_args`] The function args call() { - "./bin/$argc_func" "${argc_args[@]}" + basename="${argc_func%.*}" + lang="${argc_func##*.}" + func_path="./$lang/$basename.$lang" + if [[ ! -e "$func_path" ]]; then + _die "error: not found $argc_func" + fi + if [[ "$lang" == "sh" ]]; then + "$func_path" "${argc_args[@]}" + else + "$(_lang_to_cmd "$lang")" "./cmd/cmd.$lang" "$argc_func" + fi } -# @cmd Build all artifacts +# @cmd Build the project +# @option --names-file=functions.txt Path to a file containing function filenames, one per line. +# This file specifies which function files to build. +# Example: +# get_current_weather.sh +# may_execute_js_code.js build() { - if [[ -f functions.txt ]]; then - argc build-declarations-json + argc build-declarations-json --names-file "${argc_names_file}" + argc build-bin --names-file "${argc_names_file}" +} + +# @cmd Build bin dir +# @option --names-file=functions.txt Path to a file containing function filenames, one per line. +build-bin() { + if [[ ! -f "$argc_names_file" ]]; then + _die "no found "$argc_names_file"" fi - if [[ "$OS" = "Windows_NT" ]]; then - argc build-win-shims + mkdir -p "$BIN_DIR" + rm -rf "$BIN_DIR"/* + names=($(cat "$argc_names_file")) + invalid_names=() + for name in "${names[@]}"; do + basename="${name%.*}" + lang="${name##*.}" + func_file="$lang/$name" + if [[ -f "$func_file" ]]; then + if _is_win; then + bin_file="$BIN_DIR/$basename.cmd" + if [[ "$lang" == sh ]]; then + _build_win_sh > "$bin_file" + else + _build_win_lang $lang "$(_lang_to_cmd "$lang")" > "$bin_file" + fi + else + bin_file="$BIN_DIR/$basename" + if [[ "$lang" == sh ]]; then + ln -s "$PWD/$func_file" "$bin_file" + else + ln -s "$PWD/cmd/cmd.$lang" "$bin_file" + fi + fi + else + invalid_names+=("$name") + fi + done + if [[ -n "$invalid_names" ]]; then + _die "error: missing following functions: ${invalid_names[*]}" fi + echo "Build bin" } -# @cmd Build declarations for specific functions -# @option --output=functions.json Specify a file path to save the function declarations -# @option --names-file=functions.txt Specify a file containing function names -# @arg funcs*[`_choice_func`] The function names +# @cmd Build declarations.json +# @option --output=functions.json Path to a json file to save function declarations +# @option --names-file=functions.txt Path to a file containing function filenames, one per line. +# @arg funcs*[`_choice_func`] The function filenames build-declarations-json() { + set +e if [[ "${#argc_funcs[@]}" -gt 0 ]]; then names=("${argc_funcs[@]}" ) elif [[ -f "$argc_names_file" ]]; then names=($(cat "$argc_names_file")) fi if [[ -z "$names" ]]; then - _die "error: no specific function" + _die "error: no function for building declarations.json" fi - result=() + json_list=() + not_found_funcs=() + build_failed_funcs=() for name in "${names[@]}"; do - result+=("$(build-func-declaration "$name")") + lang="${name##*.}" + func_file="$lang/$name" + if [[ ! -f "$func_file" ]]; then + not_found_funcs+=("$name") + continue; + fi + json_data="$("build-single-declaration" "$name")" + status=$? + if [ $status -eq 0 ]; then + json_list+=("$json_data") + else + build_failed_funcs+=("$name") + fi done - echo "["$(IFS=,; echo "${result[*]}")"]" | jq '.' > "$argc_output" + if [[ -n "$not_found_funcs" ]]; then + _die "error: not found functions: ${not_found_funcs[*]}" + fi + if [[ -n "$build_failed_funcs" ]]; then + _die "error: invalid functions: ${build_failed_funcs[*]}" + fi echo "Build $argc_output" + echo "["$(IFS=,; echo "${json_list[*]}")"]" | jq '.' > "$argc_output" } -# @cmd Build declaration for a single function + +# @cmd Build single declaration # @arg func![`_choice_func`] The function name -build-func-declaration() { - argc --argc-export bin/$1 | _parse_declaration -} - -# @cmd Build shims for the functions -# Because Windows OS can't run bash scripts directly, we need to make a shim for each function -# -# @flag --clear Clear the shims -build-win-shims() { - funcs=($(_choice_func)) - for func in "${funcs[@]}"; do - echo "Shim bin/${func}.cmd" - _win_shim > "bin/${func}.cmd" - done +build-single-declaration() { + func="$1" + lang="${func##*.}" + cmd="$(_lang_to_cmd "$lang")" + if [[ "$lang" == sh ]]; then + argc --argc-export "$lang/$func" | _parse_argc_declaration + else + LLM_FUNCTION_DECLARATE=1 "$cmd" "cmd/cmd.$lang" "$func" + fi +} + +# @cmd List functions that can be put into functions.txt +# Examples: +# argc --list-functions > functions.txt +# argc --list-functions --write +# argc --list-functions search_duckduckgo.sh >> functions.txt +# @flag -w --write Output to functions.txt +# @arg funcs*[`_choice_func`] The function filenames, list all available functions if not provided +list-functions() { + if [[ -n "$argc_write" ]]; then + _choice_func > functions.txt + echo "Write functions.txt" + else + _choice_func + fi +} + +# @cmd Test the project +# @meta require-tools node,python,ruby +test() { + names_file=functions.txt.test + argc list-functions > "$names_file" + argc build --names-file "$names_file" + argc test-call-functions +} + + +# @cmd Test call functions +test-call-functions() { + if _is_win; then + ext=".cmd" + fi + "./bin/may_execute_command$ext" --command 'echo "bash works"' + argc call may_execute_command.sh --command 'echo "bash works"' + + export LLM_FUNCTION_DATA='{"code":"console.log(\"javascript works\")"}' + "./bin/may_execute_js_code$ext" + argc call may_execute_js_code.js + + export LLM_FUNCTION_DATA='{"code":"print(\"python works\")"}' + "./bin/may_execute_py_code$ext" + argc call may_execute_py_code.py + + export LLM_FUNCTION_DATA='{"code":"puts \"ruby works\""}' + "./bin/may_execute_rb_code$ext" + argc call may_execute_rb_code.rb } # @cmd Install this repo to aichat functions_dir @@ -80,7 +204,7 @@ version() { curl --version | head -n 1 } -_parse_declaration() { +_parse_argc_declaration() { jq -r ' def parse_description(flag_option): if flag_option.describe == "" then @@ -123,26 +247,66 @@ _parse_declaration() { }' } -_win_shim() { +_lang_to_cmd() { + match_lang="$1" + for item in "${LANG_CMDS[@]}"; do + lang="${item%:*}" + if [[ "$lang" == "$match_lang" ]]; then + echo "${item#*:}" + fi + done +} + +_build_win_sh() { cat <<-'EOF' @echo off setlocal -set "script_dir=%~dp0" +set "bin_dir=%~dp0" +for %%i in ("%bin_dir:~0,-1%") do set "script_dir=%%~dpi" set "script_name=%~n0" +set "script_name=%script_name%.sh" for /f "delims=" %%a in ('argc --argc-shell-path') do set "_bash_prog=%%a" -"%_bash_prog%" --noprofile --norc "%script_dir%\%script_name%" %* +"%_bash_prog%" --noprofile --norc "%script_dir%sh\%script_name%" %* EOF } +_build_win_lang() { + lang="$1" + cmd="$2" + cat <<-EOF +@echo off +setlocal + +set "bin_dir=%~dp0" +for %%i in ("%bin_dir:~0,-1%") do set "script_dir=%%~dpi" +set "script_name=%~n0" + +$cmd "%script_dir%cmd\cmd.$lang" "%script_name%.$lang" %* +EOF +} + +_is_win() { + if [[ "$OS" == "Windows_NT" ]]; then + return 0 + else + return 1 + fi +} + _choice_func() { - ls -1 bin | grep -v '\.cmd' + for item in "${LANG_CMDS[@]}"; do + lang="${item%:*}" + ls -1 $lang | grep "\.$lang$" + done } _choice_func_args() { args=( "${argc__positionals[@]}" ) - argc --argc-compgen generic "bin/${args[0]}" "${args[@]}" + if [[ "${args[0]}" == *.sh ]]; then + argc --argc-compgen generic "sh/${args[0]}" "${args[@]}" + fi } _die() { diff --git a/README.md b/README.md index 93e5ad2..bdd545b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # LLM Functions -This project allows you to enhance large language models (LLMs) with custom functions written in Bash. Imagine your LLM being able to execute system commands, access web APIs, or perform other complex tasks – all triggered by simple, natural language prompts. +This project allows you to enhance large language models (LLMs) with custom functions written in Bash/Js/Python/Ruby. Imagine your LLM being able to execute system commands, access web APIs, or perform other complex tasks – all triggered by simple, natural language prompts. ## Prerequisites @@ -18,18 +18,11 @@ Make sure you have the following tools installed: git clone https://github.com/sigoden/llm-functions ``` -**2. Build function declarations:** +**2. Build function declarations file and bin dir:** -Before using the functions, you need to generate a `./functions.json` file that describes the available functions for the LLM. - -```sh -argc build-declarations ... -``` - -Replace `...` with the actual names of your functions. Go to the [./bin](https://github.com/sigoden/llm-functions/tree/main/bin) directory for valid function names. - -> 💡 You can also create a `./functions.txt` file with each function name on a new line, Once done, simply run `argc build-declarations` without specifying the function names to automatically use the ones listed in. +First, create a `./functions.txt` file with each function name on a new line. +Then, run `argc build` to build function declarations file `./functions.json` and bin dir `./bin/`. **3. Configure your AIChat:** @@ -55,22 +48,49 @@ Now you can interact with your LLM using natural language prompts that trigger y ![image](https://github.com/sigoden/llm-functions/assets/4012553/867b7b2a-25fb-4c74-9b66-3701eaabbd1f) +## Function Types + +### Retrieve Type + +The function returns JSON data to LLM for further processing. + +AIChat does not ask permission to run the function or print the output. + +### Execute Type + +The function does not return data to LLM. Instead, they enable more complex actions, such as showing a progress bar or running a TUI application. + +AIChat will ask permission before running the function. + +![image](https://github.com/sigoden/aichat/assets/4012553/711067b8-dd23-443d-840a-5556697ab075) + +**AIChat categorizes functions starting with `may_` as `execute type` and all others as `retrieve type`.** + ## Writing Your Own Functions -Create a new Bash script in the `./bin` directory with the name of your function (e.g., `get_current_weather`) Follow the structure demonstrated in existing examples. For instance: +The project supports write functions in bash/js/python. + +### Bash + +Create a new bashscript (.e.g. `may_execute_command.sh`) in the [./sh](./sh/) directory. ```sh -# @describe Get the current weather in a given location. -# @option --location! The city and state, e.g. San Francisco, CA +#!/usr/bin/env bash +set -e + +# @describe Executes a shell command. +# @option --command~ Command to execute, such as `ls -la` main() { - curl "https://wttr.in/$(echo "$argc_location" | sed 's/ /+/g')?format=4&M" + eval $argc_shell_command } eval "$(argc --argc-eval "$0" "$@")" ``` -The relationship between flags/options and parameters in function declarations is as follows: +`llm-functions` will automatic generate function declaration.json from [comment tags](https://github.com/sigoden/argc?tab=readme-ov-file#comment-tags). + +The relationship between comment tags and parameters in function declarations is as follows: ```sh # @flag --boolean Parameter `{"type": "boolean"}` @@ -83,25 +103,92 @@ The relationship between flags/options and parameters in function declarations i # @option --array-required+ Use `+` to mark a array parameter as required ``` -**After creating your function, don't forget to rebuild the function declarations.** - -## Function Types - -### Retrieve Type - -The function returns JSON data to LLM for further processing. - -AIChat does not ask permission to run the function or print the output. - -### Execute Type +### Javascript + +Create a new javascript (.e.g. `may_execute_command.js`) in the [./js](./js/) directory. + +```js +exports.declarate = function declarate() { + return { + "name": "may_execute_js_code", + "description": "Runs the javascript code in node.js.", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Javascript code to execute, such as `console.log(\"hello world\")`" + } + }, + "required": [ + "code" + ] + } + } +} -The function does not return data to LLM. Instead, they enable more complex actions, such as showing a progress bar or running a TUI application. +exports.execute = function execute(data) { + eval(data.code) +} -AIChat will ask permission before running the function. +``` -![image](https://github.com/sigoden/aichat/assets/4012553/711067b8-dd23-443d-840a-5556697ab075) +### Python + +Create a new python script in the [./py](./py/) directory (e.g., `may_execute_py_code.py`). + +```py +def declarate(): + return { + "name": "may_execute_py_code", + "description": "Runs the python code.", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "python code to execute, such as `print(\"hello world\")`" + } + }, + "required": [ + "code" + ] + } + } + + +def execute(data): + exec(data["code"]) +``` -**AIChat categorizes functions starting with `may_` as `execute type` and all others as `retrieve type`.** +### Ruby + +Create a new ruby script in the [./rb](./rb/) directory (e.g., `may_execute_rb_code.rb`). + +```rb +def declarate + { + "name": "may_execute_rb_code", + "description": "Runs the ruby code.", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Ruby code to execute, such as `puts \"hello world\"`" + } + }, + "required": [ + "code" + ] + } + } +end + +def execute(data) + eval(data["code"]) +end +``` ## License diff --git a/bin/get_current_weather b/bin/get_current_weather deleted file mode 100755 index 47102e0..0000000 --- a/bin/get_current_weather +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -set -e - -# @describe Get the current weather in a given location. -# @option --location! The city and optionally the state or country, e.g., "London", "San Francisco, CA". - -main() { - curl -fsSL "https://wttr.in/$(echo "$argc_location" | sed 's/ /+/g')?format=4&M" -} - -eval "$(argc --argc-eval "$0" "$@")" diff --git a/bin/may_execute_command b/bin/may_execute_command deleted file mode 100755 index 6e35e3c..0000000 --- a/bin/may_execute_command +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -set -e - -# @describe Executes a shell command. -# @option --shell-command~ "Shell command to execute, such as `ls -la`" - -main() { - eval $argc_shell_command -} - -eval "$(argc --argc-eval "$0" "$@")" diff --git a/bin/search_duckduckgo b/bin/search_duckduckgo deleted file mode 100755 index c5ab565..0000000 --- a/bin/search_duckduckgo +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -set -e - -# @describe Takes in a query string and returns search result from DuckDuckGo. -# Use it to answer user questions that require dates, facts, real-time information, or news. -# This ensures accurate and up-to-date answers. - -# @meta require-tools ddgr -# @env DDG_MAX_RESULTS=5 The max results to return. -# @option --query! The query to search for. - -main() { - ddgr -n $DDG_MAX_RESULTS --json "$argc_query" -} - -eval "$(argc --argc-eval "$0" "$@")" diff --git a/bin/search_wolframalpha b/bin/search_wolframalpha deleted file mode 100755 index a2dfdc7..0000000 --- a/bin/search_wolframalpha +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -set -e - -# @describe Get an answer to a question using Wolfram Alpha. Input should the query in English. -# Use it to answer user questions that require computation, detailed facts, data analysis, or complex queries. -# This ensures accurate and precise answers. - -# @option --query! The query to search for. -# @env WOLFRAM_API_ID! - -main() { - local curl_args=( - -sSf -G - --data-urlencode "output=JSON" - --data-urlencode "format=plaintext" - --data-urlencode "input=$argc_query" - --data-urlencode "appid=$WOLFRAM_API_ID" - "https://api.wolframalpha.com/v2/query" - ) - curl "${curl_args[@]}" | \ - jq -r '.queryresult.pods[] | select(.subpods[0].plaintext != "")' -} - -eval "$(argc --argc-eval "$0" "$@")" diff --git a/cmd/cmd.js b/cmd/cmd.js new file mode 100755 index 0000000..81eedc6 --- /dev/null +++ b/cmd/cmd.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +function loadModule() { + const path = require("path"); + let func_name = process.argv[1]; + if (func_name.endsWith("cmd.js")) { + func_name = process.argv[2] + } else { + func_name = path.basename(func_name) + } + if (!func_name.endsWith(".js")) { + func_name += '.js' + } + const func_path = path.resolve(__dirname, `../js/${func_name}`) + try { + return require(func_path); + } catch { + console.log(`Invalid js function: ${func_name}`) + process.exit(1) + } +} + +if (process.env["LLM_FUNCTION_DECLARATE"]) { + const { declarate } = loadModule(); + console.log(JSON.stringify(declarate(), null, 2)) +} else { + let data = null; + try { + data = JSON.parse(process.env["LLM_FUNCTION_DATA"]) + } catch { + console.log("Invalid LLM_FUNCTION_DATA") + process.exit(1) + } + const { execute } = loadModule(); + execute(data) +} \ No newline at end of file diff --git a/cmd/cmd.py b/cmd/cmd.py new file mode 100755 index 0000000..be0fc93 --- /dev/null +++ b/cmd/cmd.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +import os +import json +import sys +import importlib.util + +def load_module(func_name): + base_dir = os.path.dirname(os.path.abspath(__file__)) + func_path = os.path.join(base_dir, f"../py/{func_name}") + if os.path.exists(func_path): + spec = importlib.util.spec_from_file_location(func_name, func_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + else: + print(f"Invalid py function: {func_name}") + sys.exit(1) + +func_name = sys.argv[0] +if func_name.endswith("cmd.py"): + func_name = sys.argv[1] +else: + func_name = os.path.basename(func_name) + +if not func_name.endswith(".py"): + func_name += ".py" + +if os.getenv("LLM_FUNCTION_DECLARATE"): + module = load_module(func_name) + declarate = getattr(module, 'declarate') + print(json.dumps(declarate(), indent=2)) +else: + data = None + try: + data = json.loads(os.getenv("LLM_FUNCTION_DATA")) + except (json.JSONDecodeError, TypeError): + print("Invalid LLM_FUNCTION_DATA") + sys.exit(1) + + module = load_module(func_name) + execute = getattr(module, 'execute') + execute(data) \ No newline at end of file diff --git a/cmd/cmd.rb b/cmd/cmd.rb new file mode 100755 index 0000000..cb67f4d --- /dev/null +++ b/cmd/cmd.rb @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby + +require 'json' +require 'pathname' + +def load_module + if __FILE__.end_with?("cmd.rb") + func_name = ARGV[0] + else + func_name = Pathname.new(__FILE__).basename.to_s + end + + func_name += '.rb' unless func_name.end_with?('.rb') + func_path = File.expand_path("../rb/#{func_name}", __dir__) + + begin + return require_relative func_path + rescue LoadError + puts "Invalid ruby function: #{func_name}" + exit 1 + end +end + +if ENV["LLM_FUNCTION_DECLARATE"] + declarate = load_module.method(:declarate) + puts JSON.pretty_generate(declarate.call) +else + begin + data = JSON.parse(ENV["LLM_FUNCTION_DATA"]) + rescue JSON::ParserError + puts "Invalid LLM_FUNCTION_DATA" + exit 1 + end + execute = load_module.method(:execute) + execute.call(data) +end \ No newline at end of file diff --git a/js/may_execute_js_code.js b/js/may_execute_js_code.js new file mode 100644 index 0000000..9575582 --- /dev/null +++ b/js/may_execute_js_code.js @@ -0,0 +1,22 @@ +exports.declarate = function declarate() { + return { + "name": "may_execute_js_code", + "description": "Runs the javascript code in node.js.", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Javascript code to execute, such as `console.log(\"hello world\")`" + } + }, + "required": [ + "code" + ] + } + } +} + +exports.execute = function execute(data) { + eval(data.code) +} diff --git a/py/may_execute_py_code.py b/py/may_execute_py_code.py new file mode 100644 index 0000000..01a59b9 --- /dev/null +++ b/py/may_execute_py_code.py @@ -0,0 +1,21 @@ +def declarate(): + return { + "name": "may_execute_py_code", + "description": "Runs the python code.", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Python code to execute, such as `print(\"hello world\")`" + } + }, + "required": [ + "code" + ] + } + } + + +def execute(data): + exec(data["code"]) diff --git a/rb/may_execute_rb_code.rb b/rb/may_execute_rb_code.rb new file mode 100644 index 0000000..a1f9821 --- /dev/null +++ b/rb/may_execute_rb_code.rb @@ -0,0 +1,22 @@ +def declarate + { + "name": "may_execute_rb_code", + "description": "Runs the ruby code.", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Ruby code to execute, such as `puts \"hello world\"`" + } + }, + "required": [ + "code" + ] + } + } +end + +def execute(data) + eval(data["code"]) +end diff --git a/sh/get_current_weather.sh b/sh/get_current_weather.sh new file mode 100755 index 0000000..47102e0 --- /dev/null +++ b/sh/get_current_weather.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +# @describe Get the current weather in a given location. +# @option --location! The city and optionally the state or country, e.g., "London", "San Francisco, CA". + +main() { + curl -fsSL "https://wttr.in/$(echo "$argc_location" | sed 's/ /+/g')?format=4&M" +} + +eval "$(argc --argc-eval "$0" "$@")" diff --git a/sh/may_execute_command.sh b/sh/may_execute_command.sh new file mode 100755 index 0000000..90db52b --- /dev/null +++ b/sh/may_execute_command.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +# @describe Executes a shell command. +# @option --command~ Command to execute, such as `ls -la` + +main() { + eval "$argc_command" +} + +eval "$(argc --argc-eval "$0" "$@")" \ No newline at end of file diff --git a/sh/search_duckduckgo.sh b/sh/search_duckduckgo.sh new file mode 100755 index 0000000..c5ab565 --- /dev/null +++ b/sh/search_duckduckgo.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -e + +# @describe Takes in a query string and returns search result from DuckDuckGo. +# Use it to answer user questions that require dates, facts, real-time information, or news. +# This ensures accurate and up-to-date answers. + +# @meta require-tools ddgr +# @env DDG_MAX_RESULTS=5 The max results to return. +# @option --query! The query to search for. + +main() { + ddgr -n $DDG_MAX_RESULTS --json "$argc_query" +} + +eval "$(argc --argc-eval "$0" "$@")" diff --git a/sh/search_wolframalpha.sh b/sh/search_wolframalpha.sh new file mode 100755 index 0000000..a2dfdc7 --- /dev/null +++ b/sh/search_wolframalpha.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -e + +# @describe Get an answer to a question using Wolfram Alpha. Input should the query in English. +# Use it to answer user questions that require computation, detailed facts, data analysis, or complex queries. +# This ensures accurate and precise answers. + +# @option --query! The query to search for. +# @env WOLFRAM_API_ID! + +main() { + local curl_args=( + -sSf -G + --data-urlencode "output=JSON" + --data-urlencode "format=plaintext" + --data-urlencode "input=$argc_query" + --data-urlencode "appid=$WOLFRAM_API_ID" + "https://api.wolframalpha.com/v2/query" + ) + curl "${curl_args[@]}" | \ + jq -r '.queryresult.pods[] | select(.subpods[0].plaintext != "")' +} + +eval "$(argc --argc-eval "$0" "$@")" -- cgit v1.2.3