- Posted on
- • Artificial Intelligence
Build an Artificial Intelligence Bash Assistant from Scratch
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Build an Artificial Intelligence Bash Assistant from Scratch
What if your terminal could answer questions, draft one-liners, and explain cryptic errors without leaving your shell? In a world of endless docs, man pages, and forum tabs, an AI Bash assistant can keep you focused and productive, turning “What’s the sed for that?” into a one-liner you can refine in seconds.
This guide shows you how to build a practical, privacy-aware Bash assistant that runs entirely from your terminal. You choose the backend: a cloud model (OpenAI-compatible) or a local model (Ollama). No heavyweight frameworks—just Bash, curl, and jq.
Why this is worth your time
Keep your hands on the keyboard: Ask and get answers in your workflow.
Control and privacy: Use a local model when you can, and the cloud when you must.
Portable and simple: One small Bash script; works across distros.
Extensible: Add context memory, modes (explain, refactor, draft), and guardrails.
What you’ll build
A single
aiBash scriptSupports either:
- OpenAI-compatible HTTP API, or
- A local Ollama model via its HTTP API
Modes for general chat and command explanation
Optional lightweight conversation memory
1) Install prerequisites
You’ll need curl and jq.
Debian/Ubuntu (apt):
sudo apt update sudo apt install -y curl jqFedora/RHEL/CentOS (dnf):
sudo dnf install -y curl jqopenSUSE (zypper):
sudo zypper refresh sudo zypper install -y curl jq
Optional (local backend): Install Ollama to run models on your machine.
curl -fsSL https://ollama.com/install.sh | sh
# Start the server (if not started automatically) and pull a model
ollama pull llama3
By default Ollama listens on http://127.0.0.1:11434.
2) Choose your backend
Pick one:
OpenAI-compatible (cloud):
- Get an API key from your provider.
- Export environment variables:
export OPENAI_API_KEY="your_api_key_here" export OPENAI_BASE_URL="https://api.openai.com/v1" # default; change if using a compatible host export OPENAI_MODEL="gpt-4o-mini" # or another available modelOllama (local):
- Ensure Ollama is running.
- Export environment variables:
export OLLAMA_HOST="127.0.0.1:11434" # default export OLLAMA_MODEL="llama3" # or any model you've pulled
You can switch backends any time by setting AI_BACKEND=openai or AI_BACKEND=ollama.
3) Create the minimal AI assistant
Save this as ~/bin/ai (or ~/.local/bin/ai) and make it executable.
#!/usr/bin/env bash
set -euo pipefail
# Minimal, single-file AI assistant for Bash
# Backends: openai (cloud) or ollama (local)
AI_BACKEND="${AI_BACKEND:-openai}" # openai | ollama
SYSTEM_PROMPT="${SYSTEM_PROMPT:-You are a concise Linux shell assistant. Favor short, correct answers with examples.}"
TMP_DIR="${XDG_RUNTIME_DIR:-/tmp}"
CONTEXT_FILE="${CONTEXT_FILE:-$TMP_DIR/ai_context.jsonl}" # optional memory
die() { echo "error: $*" >&2; exit 1; }
usage() {
cat <<EOF
Usage:
ai [--explain] [--mem] "your question"
echo "text" | ai [--explain] [--mem]
Options:
--explain Prefix the prompt with an instruction to explain, step-by-step.
--mem Use lightweight conversation memory (stores brief recent turns).
Backends:
Set AI_BACKEND=openai (default) or AI_BACKEND=ollama.
OpenAI env (if AI_BACKEND=openai):
OPENAI_API_KEY (required), OPENAI_BASE_URL (default: https://api.openai.com/v1), OPENAI_MODEL (e.g., gpt-4o-mini)
Ollama env (if AI_BACKEND=ollama):
OLLAMA_HOST (default: 127.0.0.1:11434), OLLAMA_MODEL (e.g., llama3)
EOF
}
# Parse flags
EXPLAIN=0
USE_MEM=0
ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--explain) EXPLAIN=1; shift;;
--mem) USE_MEM=1; shift;;
-h|--help) usage; exit 0;;
*) ARGS+=("$1"); shift;;
esac
done
# Read input
if [[ ${#ARGS[@]} -gt 0 ]]; then
USER_INPUT="${ARGS[*]}"
else
if [ -t 0 ]; then usage; exit 1; fi
USER_INPUT="$(cat)"
fi
if [[ $EXPLAIN -eq 1 ]]; then
USER_INPUT="Explain, step-by-step, for an experienced Linux user: ${USER_INPUT}"
fi
json_escape() { jq -Rs . <<<"$1"; } # returns a JSON string
build_messages_json() {
# Build the messages array with optional short memory from JSONL
local msgs="[ {\"role\":\"system\",\"content\":$(json_escape "$SYSTEM_PROMPT")} ]"
if [[ $USE_MEM -eq 1 && -s "$CONTEXT_FILE" ]]; then
# Take last 6 lines (3 turns), truncate each content to ~800 chars
local recent
recent="$(tail -n 6 "$CONTEXT_FILE" | sed -E 's/(.{0,800}).*/\1/')" || true
if [[ -n "$recent" ]]; then
while IFS= read -r line; do
msgs="${msgs%,}] , $line ]"
done <<<"$recent"
fi
fi
msgs="${msgs%,}] , {\"role\":\"user\",\"content\":$(json_escape "$USER_INPUT")} ]"
echo "$msgs"
}
call_openai() {
: "${OPENAI_API_KEY:?Set OPENAI_API_KEY}"
local base="${OPENAI_BASE_URL:-https://api.openai.com/v1}"
local model="${OPENAI_MODEL:-gpt-4o-mini}"
local messages
messages="$(build_messages_json)"
local payload
payload="$(jq -n --arg m "$model" --argjson msgs "$messages" '{model:$m, messages:$msgs, temperature:0.2}')"
curl -fsS "$base/chat/completions" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d "$payload"
}
call_ollama() {
local host="${OLLAMA_HOST:-127.0.0.1:11434}"
local model="${OLLAMA_MODEL:-llama3}"
local messages
messages="$(build_messages_json)"
local payload
payload="$(jq -n --arg m "$model" --argjson msgs "$messages" '{model:$m, messages:$msgs, stream:false}')"
curl -fsS "http://$host/api/chat" \
-H "Content-Type: application/json" \
-d "$payload"
}
# Dispatch
resp=""
case "$AI_BACKEND" in
openai) resp="$(call_openai)"; OUT="$(jq -r '.choices[0].message.content // empty' <<<"$resp")" ;;
ollama) resp="$(call_ollama)"; OUT="$(jq -r '.message.content // empty' <<<"$resp")" ;;
*) die "Unknown AI_BACKEND: $AI_BACKEND";;
esac
[[ -n "$OUT" ]] || { echo "$resp" >&2; die "No content returned. Check config/keys."; }
# Print the model's reply
printf "%s\n" "$OUT"
# Append to memory if requested
if [[ $USE_MEM -eq 1 ]]; then
mkdir -p "$(dirname "$CONTEXT_FILE")"
printf '{"role":"user","content":%s}\n' "$(json_escape "$USER_INPUT")" >>"$CONTEXT_FILE"
printf '{"role":"assistant","content":%s}\n' "$(json_escape "$OUT")" >>"$CONTEXT_FILE"
fi
Make it executable and add to your PATH:
chmod +x ~/bin/ai
# If needed:
mkdir -p ~/.local/bin
cp ~/bin/ai ~/.local/bin/
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Test it:
AI_BACKEND=ollama ai "Write a one-liner to list the five largest files under /var, human-readable."
AI_BACKEND=openai ai --explain "sed -E 's/([0-9]{3})/[\1]/g' file.txt"
4) Real-world examples
Explain a command before you run it:
ai --explain "find /etc -type f -name '*.conf' -mtime -3 -print"Draft a safe cleanup script (you validate before running):
ai "Draft a bash snippet that archives logs older than 14 days in /var/log/myapp to /var/log/archive using tar and gzip. Include a dry-run comment."Turn a requirement into a one-liner:
ai "Show a robust awk command to sum the 3rd column of a CSV (with header), ignoring blanks."Troubleshoot:
ai "systemd service keeps restarting with exit code 203/EXEC. What are the common causes and how do I diagnose?"
Tip: Pipe stderr or logs into ai --explain for quick context when possible.
5) Level up your assistant (optional enhancements)
Add a “shellsafe” instruction:
- Set a stronger system prompt so the model avoids destructive actions:
export SYSTEM_PROMPT="You are a cautious Linux assistant. Prefer read-only diagnostics. If a command could change data, present it with clear comments and a dry-run or noop variant."Add a shorthand alias:
echo "alias aii='AI_BACKEND=ollama ai --mem'" >> ~/.bashrcStream responses:
- Both backends can stream. For a quick take, switch to
stream:true(Ollama) orstream:true(OpenAI), then read line-by-line. Example sketch (advanced):
# For OpenAI: add "stream": true and process .choices[0].delta.content chunks from the SSE lines. # For Ollama: add "stream": true and print each JSON line's .message.content incrementally.- Both backends can stream. For a quick take, switch to
Curate memory:
- Periodically truncate
$CONTEXT_FILEor keep per-project memories:
export CONTEXT_FILE="$PWD/.ai_context.jsonl"- Periodically truncate
Guardrails:
- Never execute returned commands blindly. Consider copying with the mouse or require a manual paste.
- Prefer “explain” mode when exploring unfamiliar operations.
Troubleshooting
401/403 with OpenAI backend:
- Check
OPENAI_API_KEY,OPENAI_BASE_URL, and model name.
- Check
404/connection refused with Ollama:
- Ensure Ollama is running and the model is pulled:
ollama serve # if needed ollama list ollama pull llama3Empty replies:
- Print raw response to debug:
AI_BACKEND=openai ai "hello" 2>/dev/null | sed -n '1,40p'
Conclusion and Call to Action
You now have a fast, composable AI assistant that lives in your shell, respects your workflow, and works with either local or cloud models. Start small: ask it to explain commands or draft safe examples. Then extend it with streaming, project-scoped memory, or extra modes tailored to your work.
Your next step:
- Pick a backend, set the env vars, and run your first query:
AI_BACKEND=ollama ai --explain "What is the safest way to delete old Docker images?"If it saves you even one tab switch today, it’s already doing its job.