Files
sh-goodies/shee.sh
T

163 lines
6.2 KiB
Bash
Raw Normal View History

2025-10-19 11:55:24 +03:00
#!/bin/sh
# shee.sh: a shell-based OpenAI-compatible local chat
# Depends on curl, jq, file and mktemp, otherwise fully POSIX-compatible
# (for history/line editing, run it via rlwrap)
# Configurable in-chat and with environment variables
# Created by Luxferre in 2025, released into public domain
# all configuration variables are here
[ -z "$SHEESH_API_ROOT" ] && SHEESH_API_ROOT='https://text.pollinations.ai/openai'
[ -z "$SHEESH_MODEL" ] && SHEESH_MODEL='openai-fast'
[ -z "$SHEESH_TEMP" ] && SHEESH_TEMP='0.6'
[ -z "$SHEESH_SYSPROMPT" ] && SHEESH_SYSPROMPT='You are a helpful assistant.'
histfile="$(mktemp -t furrfu.XXXXXX)"
datafile="$(mktemp -t furrfu.XXXXXX)"
addhistory() {
role="$1"; shift
jq -nc --slurpfile h $histfile --arg r "$role" --arg a "$*" '$h[0]+[{"role":$r,"content":$a}]' > ${histfile}.new
mv ${histfile}.new $histfile
}
prompt() { printf "\e[1;32m>> \e[0m"; }
clearhist() {
printf '[]' > $histfile
addhistory system "$SHEESH_SYSPROMPT"
}
startswith() { case $1 in "$2"*) true;; *) false;; esac; }
setsys() {
SHEESH_SYSPROMPT="$*"
clearhist
}
setsysfile() { setsys "$(<"$1")"; }
addfile() {
tmp="$(mktemp -t furrfufile.XXXXXXX)"
fname="$(basename $1)"
if startswith "$1" "http*://"; then
[ -z "$fname" || "$fname" == "/" ] && $fname="${1//\//_}"
curl -sSLN $1 > $tmp
else
cp "$1" $tmp
fi
if file -b --mime-encoding $tmp | grep binary; then
uuencode -m $tmp $fname > ${tmp}.new
mv ${tmp}.new $tmp
else
echo "Contents of ${fname}:" >> ${tmp}.new
cat $tmp >> ${tmp}.new
mv ${tmp}.new $tmp
fi
jq -nc --slurpfile h $histfile --rawfile a "$tmp" '$h[0]+[{"role":"user","content":$a}]' > ${histfile}.new
mv ${histfile}.new $histfile
rm $tmp
}
listmodels() {
echo 'Available models:'
curl -sSL ${SHEESH_API_ROOT}/models \
${SHEESH_UA:+-H "User-Agent: $SHEESH_UA"} \
${SHEESH_REFERER:+-H "Referer: $SHEESH_REFERER"} \
${SHEESH_ORIGIN:+-H "Origin: $SHEESH_ORIGIN"} \
${OPENAI_API_KEY:+-H "Authorization: Bearer $OPENAI_API_KEY"} \
${SHEESH_COOKIES:+-H "Cookie: $SHEESH_COOKIES"} \
| jq -r '.data[].id' | while read -r modelname; do
printf '\e[0;36m* %s\e[0m\n' "$modelname"
done
}
help() {
printf 'Current settings:\n\n\e[2;37mEndpoint: %s\n' "$SHEESH_API_ROOT"
printf 'Model: %s\n' "$SHEESH_MODEL"
printf 'Temperature: %.2f\n' "$SHEESH_TEMP"
printf 'System prompt: "%s"\e[0m\n\n' "$SHEESH_SYSPROMPT"
echo 'Available commands:'
echo
echo '* /models: list available models'
echo '* /model: change the current model'
echo '* /add: add a file to the context'
echo '* /temp: change the current temperature'
echo '* /endpoint: change the current API endpoint'
echo '* /clear: clear the session context'
echo '* /sys [prompt]: set a system prompt and clear the context'
echo '* /sysload [file]: load the system prompt from a file and clear the context'
echo '* /bye: exit the chat immediately'
echo
echo 'Available environment variables for configuration:'
echo
echo '* OPENAI_API_KEY: API key string (default empty)'
echo '* SHEESH_API_ROOT: API endpoint (default https://text.pollinations.ai/openai)'
echo '* SHEESH_MODEL: model ID (default openai-fast)'
echo '* SHEESH_TEMP: model temperature (default 0.6)'
echo '* SHEESH_SYSPROMPT: system prompt (default "You are a helpful assistant.") '
echo '* SHEESH_UA: user agent override (default empty)'
echo '* SHEESH_REFERER: Referer header value (default empty)'
echo '* SHEESH_ORIGIN: Origin header value (default empty)'
echo '* SHEESH_COOKIES: Cookie header value (default empty)'
echo
}
trap cleanup SIGINT SIGABRT SIGHUP SIGQUIT
cleanup() {
rm $histfile $datafile
printf '\nBye!\n'
exit 0
}
hdrline='----------------------'
title='SHEE.SH v2 by Luxferre'
printf '\e[1;36m%s\n%s\n%s\e[0m\n' "$hdrline" "$title" "$hdrline"
printf '\e[2;37mEndpoint: %s\n' "$SHEESH_API_ROOT"
printf 'Model: %s\n' "$SHEESH_MODEL"
printf 'Temperature: %.2f\n' "$SHEESH_TEMP"
printf '\e[1;36m%s\e[0m\n' "$hdrline"
setsys "$SHEESH_SYSPROMPT"
while prompt && read -r line; do
[ -z "$line" ] && continue
[ "$line" == "/bye" ] && break
[ "$line" == "/clear" ] && clearhist && echo "Session cleared" && continue
[ "$line" == "/help" ] && help && continue
[ "$line" == "/models" ] && listmodels && continue
startswith "$line" '/sys ' && setsys "${line#\/sys }" && echo "System prompt set and session cleared" && continue
startswith "$line" '/sysload ' && setsysfile "${line#\/sysload }" && echo "System prompt loaded and session cleared" && continue
startswith "$line" '/temp ' && SHEESH_TEMP="${line#\/temp }" && echo "Temperature set to $SHEESH_TEMP" && continue
startswith "$line" '/model ' && SHEESH_MODEL="${line#\/model }" && echo "Model set to $SHEESH_MODEL" && continue
startswith "$line" '/endpoint ' && SHEESH_API_ROOT="${line#\/endpoint }" && echo "API endpoint set to $SHEESH_API_ROOT" && continue
if startswith "$line" '/add '; then
filelist="${line#\/add }"
for f in $filelist; do
addfile "$f"
printf "File %s added to the context\n" "$f"
done
continue
fi
# commands end, normal message processing start
addhistory user "$line"
jq -nc --arg m "$SHEESH_MODEL" --arg t "$SHEESH_TEMP" --slurpfile msgs $histfile \
'{model:$m,stream:true,messages:$msgs[0],temperature:($t|tonumber)}' > $datafile
curl -sSLN "${SHEESH_API_ROOT}/chat/completions" \
-H 'Content-Type: application/json' \
${SHEESH_UA:+-H "User-Agent: $SHEESH_UA"} \
${SHEESH_REFERER:+-H "Referer: $SHEESH_REFERER"} \
${SHEESH_ORIGIN:+-H "Origin: $SHEESH_ORIGIN"} \
${OPENAI_API_KEY:+-H "Authorization: Bearer $OPENAI_API_KEY"} \
${SHEESH_COOKIES:+-H "Cookie: $SHEESH_COOKIES"} \
-H 'Accept: text/event-stream' --data-binary "@$datafile" 2>/dev/null | \
while IFS= read -r chunk; do
[ -z "$chunk" ] && continue
data="${chunk#data: }"
[ "$data" == "[DONE]" ] && break
delta=''
startswith "$data" '{' && delta="$(printf '%s' "$data" | jq -rj '.choices[0].delta.content // empty' 2>/dev/null;printf x)"
delta="${delta%?}"
[ -n "$delta" ] && printf "\e[0;33m%s\e[0m" "$delta"
tailbuf="$tailbuf$delta"
done
echo
addhistory assistant "$tailbuf"
tailbuf=""
line=''
done
cleanup