diff --git a/agents/coder/index.yaml b/agents/coder/index.yaml index c1dcdec..08bb2f8 100644 --- a/agents/coder/index.yaml +++ b/agents/coder/index.yaml @@ -15,7 +15,7 @@ instructions: | 1. fs_mkdir: Create new directories in the project structure. 2. fs_create: Generate new files with specified contents. - 3. fs_edit: Examine and modify existing files. FULLY. + 3. fs_patch: Examine and modify existing files. 4. fs_cat: View the contents of existing files without making changes. 5. fs_ls: Understand the current project structure or locate specific files. 6. web_search: Obtain current information on technologies, libraries, or best practices. diff --git a/agents/coder/tools.sh b/agents/coder/tools.sh index 6e599e6..c56e734 100755 --- a/agents/coder/tools.sh +++ b/agents/coder/tools.sh @@ -1,64 +1,19 @@ #!/usr/bin/env bash set -e +ROOT_DIR="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" + # @env LLM_OUTPUT=/dev/stdout The output path # @cmd Create a new file at the specified path with contents. # @option --path! The path where the file should be created # @option --contents! The contents of the file fs_create() { - _guard_path "$argc_path" Create + "$ROOT_DIR/utils/guard_path.sh" "$argc_path" "Create '$argc_path'?" mkdir -p "$(dirname "$argc_path")" printf "%s" "$argc_contents" > "$argc_path" echo "File created: $argc_path" >> "$LLM_OUTPUT" } -# @cmd Apply changes to a file. Use this when you need to edit an existing file. -# YOU ALWAYS PROVIDE THE FULL FILE CONTENTS WHEN EDITING. NO PARTIAL CONTENTS OR COMMENTS. -# YOU MUST PROVIDE THE FULL FILE CONTENTS. - -# @option --path! The path of the file to edit -# @option --contents! The new contents to apply to the file -# @meta require-tools git -fs_edit() { - if [[ -f "$argc_path" ]]; then - _guard_path "$argc_path" Edit - changed=0 - printf "%s" "$argc_contents" | git diff --no-index "$argc_path" - || { - changed=1 - } - if [[ "$changed" -eq 0 ]]; then - echo "No changes detected." >> "$LLM_OUTPUT" - else - if [ -t 1 ]; then - echo - read -r -p "Apply changes? [Y/n] " ans - if [[ "$ans" == "N" || "$ans" == "n" ]]; then - echo "Aborted!" - exit 1 - fi - fi - printf "%s" "$argc_contents" > "$argc_path" - echo "Applied changes" >> "$LLM_OUTPUT" - fi - else - echo "Not found file: $argc_path" >> "$LLM_OUTPUT" - fi -} - -_guard_path() { - path="$(realpath -m "$1")" - action="$2" - if [[ ! "$path" == "$(pwd)"* ]]; then - if [ -t 1 ]; then - read -r -p "$action $path? [Y/n] " ans - if [[ "$ans" == "N" || "$ans" == "n" ]]; then - echo "Aborted!" - exit 1 - fi - fi - fi -} - # See more details at https://github.com/sigoden/argc eval "$(argc --argc-eval "$0" "$@")" diff --git a/agents/coder/tools.txt b/agents/coder/tools.txt index 9d7d6c3..f4f352a 100644 --- a/agents/coder/tools.txt +++ b/agents/coder/tools.txt @@ -1,4 +1,5 @@ fs_mkdir.sh fs_ls.sh +fs_patch.sh fs_cat.sh web_search.sh \ No newline at end of file diff --git a/agents/todo/tools.sh b/agents/todo/tools.sh index 6d31f64..48efa29 100755 --- a/agents/todo/tools.sh +++ b/agents/todo/tools.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -e +ROOT_DIR="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" + # @env LLM_OUTPUT=/dev/stdout The output path # @cmd Add a new todo item @@ -65,13 +67,7 @@ list_todos() { clear_todos() { todos_file="$(_get_todos_file)" if [[ -f "$todos_file" ]]; then - if [ -t 1 ]; then - read -r -p "Clean the entire todo list? [Y/n] " ans - if [[ "$ans" == "N" || "$ans" == "n" ]]; then - echo "Aborted!" - exit 1 - fi - fi + "$ROOT_DIR/utils/guard_operation.sh" "Clean the entire todo list?" rm -rf "$todos_file" echo "Successfully cleaned the entire todo list" >> "$LLM_OUTPUT" else diff --git a/scripts/declarations-util.sh b/scripts/declarations-util.sh index 4ee5ca8..d82f4b4 100755 --- a/scripts/declarations-util.sh +++ b/scripts/declarations-util.sh @@ -63,7 +63,7 @@ end fi fi if [[ -z "$json_type" ]]; then - echo "invalid JSON data" + echo "error: invalid JSON data" >&2 exit 1 fi } diff --git a/scripts/run-agent.sh b/scripts/run-agent.sh index 0b6ed47..dc11e58 100755 --- a/scripts/run-agent.sh +++ b/scripts/run-agent.sh @@ -53,7 +53,7 @@ load_env() { run() { if [[ -z "$agent_data" ]]; then - die "No JSON data" + die "error: no JSON data" fi if [[ "$OS" == "Windows_NT" ]]; then @@ -81,7 +81,7 @@ def to_args: EOF )" args="$(echo "$agent_data" | jq -r "$jq_script" 2>/dev/null)" || { - die "Invalid JSON data" + die "error: invalid JSON data" } no_llm_output=0 diff --git a/scripts/run-tool.sh b/scripts/run-tool.sh index 4ede4a1..a87c762 100755 --- a/scripts/run-tool.sh +++ b/scripts/run-tool.sh @@ -49,7 +49,7 @@ load_env() { run() { if [[ -z "$tool_data" ]]; then - die "No JSON data" + die "error: no JSON data" fi if [[ "$OS" == "Windows_NT" ]]; then @@ -77,7 +77,7 @@ def to_args: EOF )" args="$(echo "$tool_data" | jq -r "$jq_script" 2>/dev/null)" || { - die "Invalid JSON data" + die "error: invalid JSON data" } no_llm_output=0 diff --git a/tools/execute_command.sh b/tools/execute_command.sh index b263b10..eb58eba 100755 --- a/tools/execute_command.sh +++ b/tools/execute_command.sh @@ -6,14 +6,10 @@ set -e # @env LLM_OUTPUT=/dev/stdout The output path +ROOT_DIR="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" + main() { - if [ -t 1 ]; then - read -r -p "Are you sure you want to continue? [Y/n] " ans - if [[ "$ans" == "N" || "$ans" == "n" ]]; then - echo "Aborted!" - exit 1 - fi - fi + "$ROOT_DIR/utils/guard_operation.sh" eval "$argc_command" >> "$LLM_OUTPUT" } diff --git a/tools/execute_sql_code.sh b/tools/execute_sql_code.sh index df57dea..34cd948 100755 --- a/tools/execute_sql_code.sh +++ b/tools/execute_sql_code.sh @@ -9,15 +9,11 @@ set -e # @env USQL_DSN! The database url, e.g. pgsql://user:pass@host/dbname # @env LLM_OUTPUT=/dev/stdout The output path +ROOT_DIR="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" + main() { if ! grep -qi '^select' <<<"$argc_code"; then - if [ -t 1 ]; then - read -r -p "Are you sure you want to continue? [Y/n] " ans - if [[ "$ans" == "N" || "$ans" == "n" ]]; then - echo "Aborted!" - exit 1 - fi - fi + "$ROOT_DIR/utils/guard_operation.sh" fi usql -c "$argc_code" "$USQL_DSN" >> "$LLM_OUTPUT" } diff --git a/tools/fs_patch.sh b/tools/fs_patch.sh index 14e46d2..ce71628 100755 --- a/tools/fs_patch.sh +++ b/tools/fs_patch.sh @@ -18,22 +18,16 @@ set -e # @env LLM_OUTPUT=/dev/stdout The output path +ROOT_DIR="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" + main() { if [ ! -f "$argc_path" ]; then echo "Not found file: $argc_path" exit 1 fi - root_dir="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" - new_contents="$(awk -f "$root_dir/utils/patch.awk" "$argc_path" <(printf "%s" "$argc_contents"))" + new_contents="$(awk -f "$ROOT_DIR/utils/patch.awk" "$argc_path" <(printf "%s" "$argc_contents"))" printf "%s" "$new_contents" | git diff --no-index "$argc_path" - || true - if [ -t 1 ]; then - echo - read -r -p "Apply changes? [Y/n] " ans - if [[ "$ans" == "N" || "$ans" == "n" ]]; then - echo "Aborted!" - exit 1 - fi - fi + "$ROOT_DIR/utils/guard_operation.sh" "Apply changes?" printf "%s" "$new_contents" > "$argc_path" echo "The patch applied to: $argc_path" >> "$LLM_OUTPUT" diff --git a/tools/fs_rm.sh b/tools/fs_rm.sh index dd1e2f7..9d9386f 100755 --- a/tools/fs_rm.sh +++ b/tools/fs_rm.sh @@ -7,26 +7,14 @@ set -e # @env LLM_OUTPUT=/dev/stdout The output path +ROOT_DIR="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" + main() { if [[ -f "$argc_path" ]]; then - _guard_path "$argc_path" Remove + "$ROOT_DIR/utils/guard_path.sh" "$argc_path" "Remove '$argc_path'?" rm -rf "$argc_path" fi echo "Path removed: $argc_path" >> "$LLM_OUTPUT" } -_guard_path() { - path="$(realpath -m "$1")" - action="$2" - if [[ ! "$path" == "$(pwd)"* ]]; then - if [ -t 1 ]; then - read -r -p "$action $path? [Y/n] " ans - if [[ "$ans" == "N" || "$ans" == "n" ]]; then - echo "Aborted!" - exit 1 - fi - fi - fi -} - eval "$(argc --argc-eval "$0" "$@")" diff --git a/tools/fs_write.sh b/tools/fs_write.sh index 0e0dba0..303a77b 100755 --- a/tools/fs_write.sh +++ b/tools/fs_write.sh @@ -8,25 +8,13 @@ set -e # @env LLM_OUTPUT=/dev/stdout The output path +ROOT_DIR="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" + main() { - _guard_path "$argc_path" Write + "$ROOT_DIR/utils/guard_path.sh" "$argc_path" "Write '$argc_path'?" mkdir -p "$(dirname "$argc_path")" printf "%s" "$argc_contents" > "$argc_path" echo "The contents written to: $argc_path" >> "$LLM_OUTPUT" } -_guard_path() { - path="$(realpath -m "$1")" - action="$2" - if [[ ! "$path" == "$(pwd)"* ]]; then - if [ -t 1 ]; then - read -r -p "$action $path? [Y/n] " ans - if [[ "$ans" == "N" || "$ans" == "n" ]]; then - echo "Aborted!" - exit 1 - fi - fi - fi -} - eval "$(argc --argc-eval "$0" "$@")" diff --git a/tools/search_wikipedia.sh b/tools/search_wikipedia.sh index 0bf791e..6c7010d 100755 --- a/tools/search_wikipedia.sh +++ b/tools/search_wikipedia.sh @@ -17,7 +17,7 @@ main() { title="$(echo "$json" | jq -r '.query.search[0].title // empty')" pageid="$(echo "$json" | jq -r '.query.search[0].pageid // empty')" if [[ -z "$title" || -z "$pageid" ]]; then - echo "Error: No results found for '$argc_query'" + echo "error: no results for '$argc_query'" >&2 exit 1 fi title="$(echo "$title" | tr ' ' '_')" diff --git a/tools/send_twilio.sh b/tools/send_twilio.sh index 2a197ff..0259efb 100755 --- a/tools/send_twilio.sh +++ b/tools/send_twilio.sh @@ -31,10 +31,10 @@ main() { if [[ "$(echo "$body" | jq -r 'has("sid")')" == "true" ]]; then echo "Message sent successfully" >> "$LLM_OUTPUT" else - _die "$body" + _die "error: $body" fi else - _die "$body" + _die "error: $body" fi } diff --git a/utils/guard_operation.sh b/utils/guard_operation.sh new file mode 100755 index 0000000..1e7f1ed --- /dev/null +++ b/utils/guard_operation.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Guard an operation with a confirmation prompt. + +main() { + if [ -t 1 ]; then + confirmation_prompt="${1:-"Are you sure you want to continue?"}" + read -r -p "$confirmation_prompt [Y/n] " ans + if [[ "$ans" == "N" || "$ans" == "n" ]]; then + echo "error: aborted!" 2>&1 + exit 1 + fi + fi +} + +main "$@" diff --git a/utils/guard_path.sh b/utils/guard_path.sh new file mode 100755 index 0000000..6de9cd8 --- /dev/null +++ b/utils/guard_path.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +main() { + if [[ "$#" -ne 2 ]]; then + echo "Usage: guard_path.sh " >&2 + exit 1 + fi + if [ -t 1 ]; then + path="$(_to_realpath "$1")" + confirmation_prompt="$2" + if [[ ! "$path" == "$(pwd)"* ]]; then + read -r -p "$confirmation_prompt [Y/n] " ans + if [[ "$ans" == "N" || "$ans" == "n" ]]; then + echo "error: aborted!" >&2 + exit 1 + fi + fi + fi +} + +_to_realpath() { + path="$1" + if [[ $OS == "Windows_NT" ]]; then + path="$(cygpath -u "$path")" + fi + awk -v path="$path" -v pwd="$PWD" ' +BEGIN { + if (path !~ /^\//) { + path = pwd "/" path + } + if (path ~ /\/\.{1,2}?$/) { + isDir = 1 + } + split(path, parts, "/") + newPartsLength = 0 + for (i = 1; i <= length(parts); i++) { + part = parts[i] + if (part == "..") { + if (newPartsLength > 0) { + delete newParts[newPartsLength--] + } + } else if (part != "." && part != "") { + newParts[++newPartsLength] = part + } + } + if (isDir == 1 || newPartsLength == 0) { + newParts[++newPartsLength] = "" + } + printf "/" + for (i = 1; i <= newPartsLength; i++) { + newPart = newParts[i] + printf newPart + if (i < newPartsLength) { + printf "/" + } + } +}' +} + +main "$@" diff --git a/utils/patch.awk b/utils/patch.awk index b625d37..b651fb2 100755 --- a/utils/patch.awk +++ b/utils/patch.awk @@ -55,7 +55,7 @@ END { } if (hunkIndex == 0) { - print "No patch" > "/dev/stderr" + print "error: no patch" > "/dev/stderr" exit 1 } @@ -90,7 +90,7 @@ END { } if (hunkIndex != totalHunks + 1) { - print "Failed to patch the file" > "/dev/stderr" + print "error: unable to apply patch" > "/dev/stderr" exit 1 } }