Skip to content

Instantly share code, notes, and snippets.

@michaelneale
Created October 28, 2025 00:16
Show Gist options
  • Save michaelneale/c66d21d254d4a2186510d78bffaae004 to your computer and use it in GitHub Desktop.
Save michaelneale/c66d21d254d4a2186510d78bffaae004 to your computer and use it in GitHub Desktop.
agent.sh
#!/bin/bash
# Simple bash-based agent that can call shell commands via OpenAI API
# Requires: curl, jq, OPENAI_API_KEY environment variable
set -e
API_URL="https://api.openai.com/v1/chat/completions"
MODEL="gpt-5"
CONVERSATION_FILE="/tmp/agent_conversation_$$.json"
# Initialize conversation with system message and tools
cat > "$CONVERSATION_FILE" <<'EOF'
{
"model": "gpt-5",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant that can execute shell commands. When you need to run a command, use the execute_shell tool. Always explain what you're doing before executing commands."
}
],
"tools": [
{
"type": "function",
"function": {
"name": "execute_shell",
"description": "Execute a shell command and return its output",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The shell command to execute"
}
},
"required": ["command"]
}
}
}
]
}
EOF
# Function to execute shell command
execute_shell() {
local cmd="$1"
echo "πŸ”§ Executing: $cmd" >&2
output=$(eval "$cmd" 2>&1) || true
echo "$output"
}
# Function to add message to conversation
add_message() {
local role="$1"
local content="$2"
local temp_file=$(mktemp)
jq --arg role "$role" --arg content "$content" \
'.messages += [{role: $role, content: $content}]' \
"$CONVERSATION_FILE" > "$temp_file"
mv "$temp_file" "$CONVERSATION_FILE"
}
# Function to add tool call response
add_tool_response() {
local tool_call_id="$1"
local output="$2"
local temp_file=$(mktemp)
jq --arg tool_call_id "$tool_call_id" --arg output "$output" \
'.messages += [{role: "tool", tool_call_id: $tool_call_id, content: $output}]' \
"$CONVERSATION_FILE" > "$temp_file"
mv "$temp_file" "$CONVERSATION_FILE"
}
# Function to add assistant message with tool calls
add_assistant_with_tools() {
local response="$1"
local temp_file=$(mktemp)
jq --argjson response "$response" \
'.messages += [$response]' \
"$CONVERSATION_FILE" > "$temp_file"
mv "$temp_file" "$CONVERSATION_FILE"
}
# Function to call OpenAI API
call_api() {
local payload=$(jq -c '.' "$CONVERSATION_FILE")
response=$(curl -s "$API_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d "$payload")
echo "$response"
}
# Main interaction loop
echo "πŸ€– Simple Bash Agent (type 'exit' or 'quit' to end)"
echo "================================================"
while true; do
# Get user input
echo ""
echo -n "You: "
read -r user_input
# Check for exit
if [[ "$user_input" == "exit" ]] || [[ "$user_input" == "quit" ]]; then
echo "πŸ‘‹ Goodbye!"
rm -f "$CONVERSATION_FILE"
exit 0
fi
# Skip empty input
if [[ -z "$user_input" ]]; then
continue
fi
# Add user message
add_message "user" "$user_input"
# Agent loop - keep calling API until we get a final response
while true; do
# Call API
api_response=$(call_api)
# Check for API errors
if echo "$api_response" | jq -e '.error' > /dev/null 2>&1; then
error_msg=$(echo "$api_response" | jq -r '.error.message')
echo "❌ API Error: $error_msg" >&2
break
fi
# Extract the assistant's message
assistant_msg=$(echo "$api_response" | jq '.choices[0].message')
finish_reason=$(echo "$api_response" | jq -r '.choices[0].finish_reason')
# Check if there are tool calls
tool_calls=$(echo "$assistant_msg" | jq '.tool_calls // []')
tool_calls_count=$(echo "$tool_calls" | jq 'length')
if [[ "$tool_calls_count" -gt 0 ]]; then
# Add assistant message with tool calls
add_assistant_with_tools "$assistant_msg"
# Execute each tool call
for i in $(seq 0 $((tool_calls_count - 1))); do
tool_call=$(echo "$tool_calls" | jq ".[$i]")
tool_call_id=$(echo "$tool_call" | jq -r '.id')
function_name=$(echo "$tool_call" | jq -r '.function.name')
arguments=$(echo "$tool_call" | jq -r '.function.arguments')
if [[ "$function_name" == "execute_shell" ]]; then
command=$(echo "$arguments" | jq -r '.command')
output=$(execute_shell "$command")
add_tool_response "$tool_call_id" "$output"
fi
done
# Continue loop to get next response
continue
else
# No tool calls, this is the final response
content=$(echo "$assistant_msg" | jq -r '.content // ""')
if [[ -n "$content" ]]; then
echo ""
echo "Agent: $content"
# Add assistant message to conversation
add_message "assistant" "$content"
fi
break
fi
done
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment