Skip to content

Instantly share code, notes, and snippets.

@johnfmorton
Last active April 7, 2026 17:10
Show Gist options
  • Select an option

  • Save johnfmorton/9961b6f97954067396a2c338269f3d76 to your computer and use it in GitHub Desktop.

Select an option

Save johnfmorton/9961b6f97954067396a2c338269f3d76 to your computer and use it in GitHub Desktop.
A zsh function for chatting with local Gemma 4 via Ollama — streaming output, Glow markdown rendering, conversation history, and @filename / PDF support.
# the following function goes in your .zshrc file to add "gemma" to your command line
# see https://supergeekery.ddev.site/blog/running-googles-gemma-4-locally-on-macos-with-ollama
# and https://supergeekery.com/blog/upgrading-your-local-gemma-setup
# for more details
gemma() {
setopt local_options no_monitor no_notify
if ! curl -sf http://localhost:11434/ > /dev/null 2>&1; then
echo "Starting Ollama service..."
brew services start ollama
while ! curl -sf http://localhost:11434/ > /dev/null 2>&1; do
sleep 1
done
fi
local has_glow=false
command -v glow > /dev/null 2>&1 && has_glow=true
local _gemma_system='Respond in plain Markdown only. Do not use LaTeX or math delimiters ($...$, \(...\), \[...\]). Write units and symbols as Unicode directly (e.g. 0°C, 32°F, π, ², ³, ½) instead of \circ, \text{}, \frac, etc.'
# One-shot mode: pass prompt directly
if [[ $# -gt 0 ]]; then
local _prompt="$*"
local _w _fp _fc
for _w in ${(z)_prompt}; do
if [[ "$_w" == @* ]]; then
_fp="${_w#@}"
if [[ -f "$_fp" ]]; then
if [[ "$_fp" == *.pdf ]]; then
if command -v pdftotext > /dev/null 2>&1; then
echo "Reading PDF: ${_fp##*/}... (this may take a moment)"
_fc=$(pdftotext "$_fp" -)
else
echo "Warning: install poppler for PDF support (brew install poppler)"
return 1
fi
else
_fc=$(cat "$_fp")
fi
_prompt="${_prompt//$_w/$'\n\n--- Contents of '"$_fp"$' ---\n'"$_fc"$'\n--- End of '"$_fp"$' ---\n'}"
else
echo "File not found: $_fp"
echo "Use the full path, e.g. @\$HOME/Downloads/$_fp"
return 1
fi
fi
done
# Use the HTTP API directly so we get clean text (no TUI escape codes
# that `ollama run` emits even when redirected). Run curl in the
# background and show a spinner until it finishes.
local _out_file _payload
_out_file=$(mktemp)
_payload=$(jq -n --arg s "$_gemma_system" --arg p "$_prompt" \
'{"model": "gemma4:26b", "messages": [{"role":"system","content":$s},{"role":"user","content":$p}], "stream": false}')
curl -sS http://localhost:11434/api/chat -d "$_payload" > "$_out_file" 2>&1 &
local _curl_pid=$!
local _spinner='|/-\'
local _i=0
printf '\033[?25l'
while kill -0 "$_curl_pid" 2>/dev/null; do
printf '\r\033[2;90mThinking... %s\033[0m' "${_spinner:_i++%${#_spinner}:1}"
sleep 0.1
done
wait "$_curl_pid"
local _status=$?
printf '\r\033[K\033[?25h'
if [[ $_status -ne 0 ]]; then
cat "$_out_file"
rm -f "$_out_file"
return $_status
fi
local _content
_content=$(jq -r '.message.content // empty' "$_out_file" 2>/dev/null)
if [[ -z "$_content" ]]; then
echo "Error: no response from model"
cat "$_out_file"
rm -f "$_out_file"
return 1
fi
if $has_glow; then
printf '%s\n' "$_content" | glow
else
printf '%s\n' "$_content"
fi
rm -f "$_out_file"
return
fi
# Interactive mode with spinner + glow render
local tmpfile=$(mktemp)
local response_file=""
local prompt expanded content _w _fp _fc
_gemma_cleanup() {
printf '\033[0m'
rm -f "$tmpfile" "$tmpfile.tmp" "$response_file"
trap - INT TERM
}
trap '_gemma_cleanup; return' INT TERM
jq -n --arg s "$_gemma_system" '[{"role":"system","content":$s}]' > "$tmpfile"
echo "Chat with Gemma (type 'exit' or Ctrl-C to quit)"
while true; do
printf "\n>>> "
read -r prompt || break
[[ -z "$prompt" || "$prompt" == "exit" ]] && break
# Expand @filename references to file contents
expanded="$prompt"
local _file_error=false
for _w in ${(z)prompt}; do
if [[ "$_w" == @* ]]; then
_fp="${_w#@}"
if [[ -f "$_fp" ]]; then
if [[ "$_fp" == *.pdf ]]; then
if command -v pdftotext > /dev/null 2>&1; then
echo "Reading PDF: ${_fp##*/}... (this may take a moment)"
_fc=$(pdftotext "$_fp" -)
else
echo "Warning: install poppler for PDF support (brew install poppler)"
_file_error=true
break
fi
else
_fc=$(cat "$_fp")
fi
expanded="${expanded//$_w/$'\n\n--- Contents of '"$_fp"$' ---\n'"$_fc"$'\n--- End of '"$_fp"$' ---\n'}"
else
echo "File not found: $_fp (use the full path, e.g. @/Users/john/Downloads/$_fp)"
_file_error=true
break
fi
fi
done
if $_file_error; then
continue
fi
jq --arg p "$expanded" '. + [{"role": "user", "content": $p}]' "$tmpfile" > "$tmpfile.tmp" \
&& mv "$tmpfile.tmp" "$tmpfile"
# Non-streaming request with spinner (matches one-shot mode)
response_file=$(mktemp)
local _payload=$(jq -n --argjson msgs "$(cat "$tmpfile")" \
'{"model": "gemma4:26b", "messages": $msgs, "stream": false}')
curl -sS http://localhost:11434/api/chat -d "$_payload" > "$response_file" 2>&1 &
local _curl_pid=$!
local _spinner='|/-\' _i=0
printf '\033[?25l'
while kill -0 "$_curl_pid" 2>/dev/null; do
printf '\r\033[2;90mThinking... %s\033[0m' "${_spinner:_i++%${#_spinner}:1}"
sleep 0.1
done
wait "$_curl_pid"
printf '\r\033[K\033[?25h'
content=$(jq -r '.message.content // empty' "$response_file" 2>/dev/null)
if [[ -z "$content" ]]; then
echo "Error: no response from model"
rm -f "$response_file"
continue
fi
jq --arg c "$content" '. + [{"role": "assistant", "content": $c}]' "$tmpfile" > "$tmpfile.tmp" \
&& mv "$tmpfile.tmp" "$tmpfile"
if $has_glow; then
printf '%s' "$content" | glow
else
printf '%s\n' "$content"
fi
rm -f "$response_file"
done
_gemma_cleanup
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment