From 79549492695bf47ecbc04a090a3b4b8aee3deff0 Mon Sep 17 00:00:00 2001 From: Luxferre Date: Sun, 19 Oct 2025 11:55:24 +0300 Subject: [PATCH] first commit --- README.md | 11 ++++ dt | 106 +++++++++++++++++++++++++++++++++ flow.sh | 60 +++++++++++++++++++ ghmd.sh | 157 +++++++++++++++++++++++++++++++++++++++++++++++++ lshar.sh | 70 ++++++++++++++++++++++ outport | 31 ++++++++++ shee.sh | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++ shellbeat.sh | 23 ++++++++ 8 files changed, 620 insertions(+) create mode 100644 README.md create mode 100755 dt create mode 100755 flow.sh create mode 100755 ghmd.sh create mode 100644 lshar.sh create mode 100755 outport create mode 100755 shee.sh create mode 100755 shellbeat.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..31edfe8 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# sh-goodies: a useful collection of single-file, POSIX-compatible shell scripts + +## List + +* [DaemonTamer V2](./dt): local service manager to daemonize any command +* [Outport](./outport): expose reverse tunnels via SSH +* [shee.sh](./shee.sh): simple LLM chat application for OpenAI-compatible endpoints +* [ShellFlow](./flow.sh): simple workflow orchestration script +* [LShar](./lshar.sh): create self-extracting archives in the form of shell scripts +* [ShellBeat](./shellbeat.sh): a [bytebeat](https://stellartux.github.io/websynth/guide.html) audio formula player in pure shell (also depends on xxd) +* [GHMD](./ghmd.sh): a command suite to set up your own self-hosted Git repositories diff --git a/dt b/dt new file mode 100755 index 0000000..17fca71 --- /dev/null +++ b/dt @@ -0,0 +1,106 @@ +#!/bin/sh +# DaemonTamer: simple and portable local service manager in pure sh +# that allows to quickly daemonize any command and manage it as a service +# Depends on POSIX versions of sh, nohup, cut and grep +# the unreg option also depends on mktemp (not POSIX but present everywhere) +# Run sh dt help to see the parameters +# Created by Luxferre in 2025, released into public domain with no warranties + +err() { + echo $* + exit 1 +} + +msgexit() { + echo $* + exit 0 +} + +find_service() { + sline="$(grep -E "^$1\s+" $CONF)" + [ -z "$sline" ] && err "No such service found in the config!" + echo "$sline" +} + +CONF="$HOME/.dt.conf" +TMPDIR="$(dirname $(mktemp -u))" +srv_name="$2" +[ "$1" != "help" ] && [ -z "$srv_name" ] && err "Service name is required!" +pidfile="$TMPDIR/$srv_name.pid" +case $1 in +help) + echo 'DaemonTamer v2' + echo '--------------' + echo "Usage: $0 {reg|unreg|start|stop|restart} SERVICE_NAME [REG_PARAMS]" + echo 'Parameters for the reg subcommand:' + echo "$0 reg 'COMMAND' [WORKDIR] [STDOUT_LOG_FILE] [STDERR_LOG_FILE] [PRESTART] [POSTSTART] [PRESTOP] [POSTSTOP]" + echo '(the COMMAND must be specified in single quotes and without nohup)' + echo + echo 'Created by Luxferre in 2025, released into public domain with no warranties' + ;; +reg) + srv_cmd="$3" + [ -z "$srv_cmd" ] && err "Service runner command is required!" + srv_wdir="$4" + srv_outlog="$5" + srv_errlog="$6" + srv_prestart="$7" + srv_poststart="$8" + srv_prestop="$9" + srv_poststop="$10" + [ -z "$srv_wdir" ] && srv_wdir='-' + [ -z "$srv_outlog" ] && srv_outlog='/dev/null' + [ -z "$srv_errlog" ] && srv_errlog='/dev/null' + [ -z "$srv_prestart" ] && srv_prestart='-' + [ -z "$srv_poststart" ] && srv_poststart='-' + [ -z "$srv_prestop" ] && srv_prestop='-' + [ -z "$srv_poststop" ] && srv_poststop='-' + printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$srv_name" "$srv_cmd" \ + "$srv_wdir" "$srv_outlog" "$srv_errlog" "$srv_prestart" "$srv_poststart" \ + "$srv_prestop" "$srv_poststop" >> $CONF + echo "Service $srv_name registered" + ;; +unreg) + srvline="$(find_service "$srv_name")" + TCONF="$(mktemp)" + grep -v "$srv_name" $CONF > $TCONF && mv $TCONF $CONF + echo "Service $srv_name unregistered" + ;; +start) + [ -f "$pidfile" ] && msgexit "Service $srv is already running"; + srvline="$(find_service "$srv_name")" + srv_cmd="$(echo "$srvline" | cut -f2)" + srv_wdir="$(echo "$srvline" | cut -f3)" + srv_outlog="$(echo "$srvline" | cut -f4)" + srv_errlog="$(echo "$srvline" | cut -f5)" + srv_prestart="$(echo "$srvline" | cut -f6)" + srv_poststart="$(echo "$srvline" | cut -f7)" + echo "Starting service $srv_name" + [ "$srv_wdir" != "-" ] && cd "$srv_wdir" + [ "$srv_prestart" != "-" ] && $SHELL -c "$srv_prestart" + nohup $srv_cmd >$srv_outlog 2>$srv_errlog & echo $! > $pidfile + [ "$srv_poststart" != "-" ] && $SHELL -c "$srv_poststart" + echo "Service $srv_name started" + ;; +stop) + [ ! -f "$pidfile" ] && msgexit "Service $srv is not running"; + srvline="$(find_service "$srv_name")" + srv_prestop="$(echo "$srvline" | cut -f8)" + srv_poststop="$(echo "$srvline" | cut -f9)" + echo "Stopping service $srv_name" + [ "$srv_prestop" != "-" ] && $SHELL -c "$srv_prestop" + kill $(cat $pidfile) + rm -f $pidfile + [ "$srv_poststop" != "-" ] && $SHELL -c "$srv_poststop" + echo "Service $srv_name stopped" + ;; +restart) + srvline="$(find_service "$srv_name")" + $SHELL $0 stop $srv_name + $SHELL $0 start $srv_name + ;; +*) + echo 'Invalid operation! Pick start, stop, restart, reg, unreg or help' + ;; +esac + diff --git a/flow.sh b/flow.sh new file mode 100755 index 0000000..548abc9 --- /dev/null +++ b/flow.sh @@ -0,0 +1,60 @@ +#!/bin/sh +# +# ShellFlow: the simplest workflow orchestrator in under +# 20 SLOC of POSIX shell, using Unix pipelines +# +# Usage: echo some_input | /path/to/flow.sh /path/to/workflow +# +# Exposes the following variables to workflows and tasks: +# - SFLIB - resolved path to this file +# - WFDIR - directory of the workflow +# +# Also exposes the debugout function to output to stderr +# +# Typical workflow script structure: ./task1 | ./task2 | ./task3 ... +# (the workflow directory becomes the current working directory) +# +# Data format: workflow name, tab character, all other input +# +# Typical task script structure (if shell-based): +# #!/bin/sh +# . $SFLIB # source the library part of the script +# do_task() { # accepts workflow name into $1 and all input into $2 +# # your actions here +# } +# run-task +# +# You can call different workflows from inside tasks by referring to +# this very script using the $SFLIB environment variable or by calling +# the sfrun function +# +# Created by Luxferre in 2025, released into public domain + +# ShellFlow task function library + +# print to stderr +debugout() { printf '%s\n' "$*" >&2; } + +# format the message and print to stdout +flowout() { printf '%s\t%s' "$1" "$2"; } + +# use this in all task definitions +run-task() { + input="$(cat)" + wf="$(printf '%s' "$input" | cut -f 1)" + body="$(printf '%s' "$input" | cut -f 2-)" + flowout "$wf" "$(do_task "$wf" "$body")" +} + +# ShellFlow entry point + +sfrun() { + SFLIB="$(readlink -f "$0")" + wfbase="$(basename "$1")" + WFDIR="$(readlink -f $(dirname -- "$1"))" + cd "$WFDIR" + debugout "Starting workflow $wfbase..." + flowout $wfbase "$(cat)" | SFLIB="$SFLIB" WFDIR="$WFDIR" $SHELL $wfbase | cut -f 2- +} + +[ "$(basename "$0")" == "flow.sh" ] && sfrun $* diff --git a/ghmd.sh b/ghmd.sh new file mode 100755 index 0000000..d2ef700 --- /dev/null +++ b/ghmd.sh @@ -0,0 +1,157 @@ +#!/bin/sh +# GHMD is a shell script for setting up any of the following things: +# 1) a Git user on a remote instance (initial setup) +# 2) a private Git repo on the remote instance +# 3) a public Git repo using git:// protocol on the remote instance (if it uses systemd) +# 4) Git daemon (serving git:// protocol) on the remote instance +# 5) access via the Git daemon to individual repositories +# 6) repository description and clone URL for some services like stagit +# Usage: ghmd.sh [newsrv | newrepo | gitd-sd | gitd-publish | gitd-unpublish | set-repo-desc | set-repo-url | set-repo-owner] user@host [name] ("[desc|url]") +# regardless of the actions, the user must have root permissions +# prerequisite: git package must be already installed on the server +# Created by Luxferre in 2024, released into public domain + +GITUSR="git" +PARAM="$1" +USERHOST="$2" +THOST="${USERHOST##*@}" +SSHCMD="ssh $USERHOST" # instance ssh cmd + +help() { +cat < /home/${GITUSR}/.ssh/authorized_keys; chown -R ${GITUSR} /home/${GITUSR}/.ssh" + echo "Now, you'll need to enter the newly created ${GITUSR} user password:" + ssh-copy-id ${GITUSR}@${THOST} + $SSHCMD "chsh $GITUSR -s $(which git-shell)" + echo "Git user set up on ${THOST}. Now use $0 newrepo ${GITUSR}@${THOST} [name] to set up new Git repos" + ;; + "newrepo") # set up a new Git repo + [[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1 + REPONAME="$3" + [[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1 + REPODIR="${REPONAME}.git" + FULLREPODIR="/home/${GITUSR}/$REPODIR" + $SSHCMD "mkdir -p $FULLREPODIR;cd $FULLREPODIR;git init --bare --shared;chown -R ${GITUSR}:${GITUSR} ." + echo "Empty repository created at ${USERHOST}:${REPODIR}" + ;; + "set-repo-desc") # set the repo description for some services + [[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1 + REPONAME="$3" + [[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1 + VALUE="$4" # it may be empty + REPODIR="${REPONAME}.git" + FULLREPODIR="/home/${GITUSR}/$REPODIR" + $SSHCMD "cd $FULLREPODIR;echo \"$VALUE\" > description;chown -R ${GITUSR}:${GITUSR} description" + echo "Description set for ${REPODIR}" + ;; + "set-repo-url") # set the repo clone URL for some services + [[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1 + REPONAME="$3" + [[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1 + VALUE="$4" # it may be empty + REPODIR="${REPONAME}.git" + FULLREPODIR="/home/${GITUSR}/$REPODIR" + $SSHCMD "cd $FULLREPODIR;echo \"$VALUE\" > url;chown -R ${GITUSR}:${GITUSR} url" + echo "Clone URL set for ${REPODIR}" + ;; + "set-repo-owner") # set the repo owner name for some services + [[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1 + REPONAME="$3" + [[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1 + VALUE="$4" # it may be empty + REPODIR="${REPONAME}.git" + FULLREPODIR="/home/${GITUSR}/$REPODIR" + $SSHCMD "cd $FULLREPODIR;echo \"$VALUE\" > owner;chown -R ${GITUSR}:${GITUSR} owner" + echo "Owner name set for ${REPODIR}" + ;; + "gitd-sd") # set up the Git daemon using a systemd unit + [[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1 + TMPUNIT="/tmp/git-daemon.service" +cat << EOF > $TMPUNIT +[Unit] +Description=Start Git Daemon + +[Service] +ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/home/${GITUSR} /home/${GITUSR} +Restart=always +RestartSec=500ms +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=git-daemon +User=$GITUSR +Group=$GITUSR + +[Install] +WantedBy=multi-user.target +EOF + scp $TMPUNIT ${USERHOST}:/etc/systemd/system/git-daemon.service + $SSHCMD "systemctl enable git-daemon; systemctl start git-daemon" + echo "Git daemon installed on ${THOST} via systemd" + rm -f $TMPUNIT + ;; + "gitd-publish") # make a repository public via the Git daemon + [[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1 + REPONAME="$3" + [[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1 + REPODIR="${REPONAME}.git" + ACCFILE="/home/${GITUSR}/${REPODIR}/git-daemon-export-ok" + $SSHCMD "touch $ACCFILE; chown $GITUSR $ACCFILE" + echo "Repository git://${THOST}/$REPODIR made public" + ;; + "gitd-unpublish") # revoke repository publishing via the Git daemon + [[ -z "$USERHOST" ]] && echo "Error: no user and hostname!" && exit 1 + REPONAME="$3" + [[ -z "$REPONAME" ]] && echo "Error: no repository name!" && exit 1 + REPODIR="${REPONAME}.git" + ACCFILE="/home/${GITUSR}/${REPODIR}/git-daemon-export-ok" + $SSHCMD "rm -f $ACCFILE" + echo "Repository git://${THOST}/$REPODIR made private" + ;; + *) + help +esac + diff --git a/lshar.sh b/lshar.sh new file mode 100644 index 0000000..2b178aa --- /dev/null +++ b/lshar.sh @@ -0,0 +1,70 @@ +#!/bin/sh +# lshar.sh - Luxferre's shell archiver +# Outputs to stdout only (for GNU and BSD shar compatibility) +# No external deps for .shars (sh commands required: mkdir, printf) +# Archive creation also depends on sort (Busybox, Toybox or GNU Coreutils) +# Works with any text or binary files and directory structures +# Created by Luxferre in 2023, released into public domain + +# a portable, compact, no-deps serializer of any file into printf sequence +f2p() { + c=0 # init counter + l="$2" # get chunk length + [ -z "$l" ] && l=4096 # 4096-byte chunks by default (shouldn't be a problem) + cprefix="printf '%b' '" # pattern to prepend to every chunk + csuffix="' >> '$1'\n" # pattern to append to every chunk + printf '%b' "$cprefix" # always start with the prefix + IFS='' # separator + while read -r -s -d '' -n1 b; do # convert every byte + if [ "$c" == "$l" ]; then # roll over and output suffix+prefix + c=0 # reinit counter + printf '%b' "$csuffix" # end the chunk + printf '%b' "$cprefix" # begin a new chunk + else + c="$(( c + 1 ))" # increment c + fi + bcode="$(printf '%d' "'$b")" # get the ASCII code of the byte + if [ "$bcode" -gt 32 ] && [ "$bcode" -lt 127 ] && + [ "$bcode" -ne 39 ] && [ "$bcode" -ne 92 ] + then # printable and not a single quote or a backslash + printf '%b' "$b" # codes from 34 to 126 are safe to print + else # subject to escaping, print the hex representation + printf '\\x%02x' "$bcode" + fi + done <"$1" + printf '%b' "$csuffix" # always end with the suffix +} + +# main code +LF=$'\n' # cache the linefeed character +flist='' # pre-build the file list from args +dlist='' # pre-build the directory list from args +for i; do # iterate over args + bname="${i##*/}" # get the basename + if [ -d "$i" ]; then # it's a directory, we skip . and .. + [ "$bname" == '.' ] || [ "$bname" == '..' ] && continue + dlist="${dlist}${i%/}${LF}" # append the directory + else # it's a file + dname="${i%%$bname}" # get the basedir + dlist="${dlist}${dname%/}${LF}" # append the basedir + flist="${flist}${i}${LF}" # append the file + fi +done +flist="$(printf '%s' "$flist" | sort -u)" # remove file duplicates +dlist="$(printf '%s' "$dlist" | sort -u)" # remove directory duplicates +printf '%s\n' '#!/bin/sh' # output the shebang, just in case +IFS="$LF" # update the separator +for indir in $dlist; do # create the directory structure first + [ -z "$indir" ] && continue # skip empty names + printf '# dir: %s\n' "$indir" + printf "printf 'c - %%s\\\n' '%s'\n" "$indir" # announce creation + printf "mkdir -p '%s' > /dev/null 2>&1\n" "$indir" # create the dir +done +for infile in $flist; do # iterate over the file list afterwards + [ -z "$infile" ] && continue # skip empty names + printf '# file: %s\n' "$infile" + printf "printf 'x - %%s\\\n' '%s'\n" "$infile" # announce unrolling + printf "printf '' > '%s'\n" "$infile" # clear the file + f2p "$infile" # run the serializer + IFS="$LF" # update the separator +done diff --git a/outport b/outport new file mode 100755 index 0000000..c94c626 --- /dev/null +++ b/outport @@ -0,0 +1,31 @@ +#!/bin/sh +# Outport: a simple script to expose reverse tunnels via SSH +# Usage: outport.sh {start|stop} user@host [port1] [port2] ... +# Created by Luxferre in 2025, released into public domain +SOCKET_PATH=/var/run/sshcontrol.sock +ACTION="$1" +TARGET="$2" +shift +shift +PORT_OPTS="" +case "$ACTION" in + start) + while :; do + [ -z "$1" ] && break + PORT_OPTS="$PORT_OPTS -R :$1:localhost:$1" + shift + done + MISC_OPTS="-o ServerAliveInterval=60 -o ServerAliveCountMax=15" + ssh -f -N -M -S $SOCKET_PATH $PORT_OPTS $MISC_OPTS $TARGET + echo "Tunnel started" + ;; + stop) + ssh -S $SOCKET_PATH -O cancel $TARGET + ssh -S $SOCKET_PATH -O exit $TARGET + echo "Tunnel stopped" + ;; + *) + echo "Invalid action (specify start or stop)" + ;; +esac + diff --git a/shee.sh b/shee.sh new file mode 100755 index 0000000..665cc8d --- /dev/null +++ b/shee.sh @@ -0,0 +1,162 @@ +#!/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 diff --git a/shellbeat.sh b/shellbeat.sh new file mode 100755 index 0000000..e893d04 --- /dev/null +++ b/shellbeat.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# ShellBeat: a Busybox-compatible Bytebeat player with purely shell arithmetic syntax +# depends on xxd only +# By default, SoX is also required, or change the PLAYCMD variable to your engine +# (see some commented examples, some of them only accept 8Khz sample rate) +# Usage: shellbeat.sh 'formula'[ samplerate=8000] +# Example test on "42 melody": sh shellbeat.sh 't * (42 & (t >> 10))' +# Created in 2023 by Luxferre, released into public domain +FORMULA="$1" +SAMPLERATE="$2" +[ -z "$FORMULA" ] && printf 'No formula!\n' && exit +[ -z "$SAMPLERATE" ] && SAMPLERATE=8000 + +PLAYCMD="play -q -tu8 -r${SAMPLERATE} -c1 -" # most universal option with SoX play command +# or else uncomment one of these if you don't have SoX: +#PLAYCMD="tee /dev/dsp" # if you have bare OSS (only 8KHz input) +#PLAYCMD="aplay -q -f U8 -r ${SAMPLERATE}" # if you have ALSA +#PLAYCMD="pacat --raw --format=u8 --rate=${SAMPLERATE} --channels=1" # if you have PulseAudio +#PLAYCMD="pw-play --format=u8 --rate=${SAMPLERATE} --channels=1 -" # if you have PipeWire + +printf 'ShellBeat by Luxferre\nPlaying formula: %s\nSample rate: %d Hz\n' "$FORMULA" "$SAMPLERATE" +(t=0; while true; do printf '%02X\n' "$((255&(${FORMULA})))"; t=$((t+1)); done) | xxd -r -p | $PLAYCMD > /dev/null +