ollie/scripts/s/sh.rc

277 lines
5.8 KiB
Bash
Executable File

#!/usr/bin/env rc
# sh: interactive chat interface for ollie AI agents.
MOUNT=/home/lkn/mnt/ollie
if(! ~ $#OLLIE 0) MOUNT=$OLLIE
LAST=$home/.config/ollie/last-session
fn load_session {
awk -F'\t' -v d=$1 'NF==2 && $1==d {print $2; exit}' $LAST >[2]/dev/null
}
fn save_session {
dir=$1
sessid=$2
mkdir -p `{dirname $LAST}
tmp=`{mktemp}
if(test -f $LAST)
awk -F'\t' -v d=$dir 'NF==2 && $1!=d' $LAST > $tmp
printf '%s\t%s\n' $dir $sessid >> $tmp
mv $tmp $LAST
}
cmd=`{basename $0}
fn die {
echo $* >[1=2]
exit 1
}
fn is_idle {
state=`{cat $S/state >[2]/dev/null | tr -d '[:space:]'}
test $state = idle
}
fn usage {
cat <<'EOF'
usage: sh [flags]
Flags:
-new always create a new session
-resume resume last session for cwd; error if none
-session ID attach to a specific session by ID
-name NAME name for new session
-cwd DIR working directory (default: pwd)
-backend BACKEND backend override (e.g. ollama, anthropic)
-model MODEL model override
-agent AGENT agent config name
-help show this help
Input at the prompt:
<text> submit prompt, stream response
/stop interrupt the current agent turn
/kill kill the session and exit
/compact compact conversation history
/clear clear conversation history
/rn <name> rename the session
/cwd [path] get or set working directory
/agent [name] get or set agent
/model [name] get or set model
/backend [name] get or set backend
/models list available models
/backends list available backends
/mcp list MCP servers
/usage show token usage
/ctxsz show context size
/q <text> enqueue prompt for later
! <cmd> run shell command in session cwd
Ctrl-C interrupt running turn (or exit if idle)
EOF
exit 0
}
new=0
resume=0
while(! ~ $#* 0) {
switch($1) {
case -new
new=1
case -resume
resume=1
case -session
id_flag=$2
shift
case -name
name=$2
shift
case -backend
backend=$2
shift
case -model
model=$2
shift
case -agent
agent=$2
shift
case -cwd
cwd=$2
shift
case -help
usage
case *
die 'unknown flag: '^$1
}
shift
}
_dir=`{pwd}
if(! ~ $#cwd 0) _dir=$cwd
fn write_new {
{
if(! ~ $#name 0) printf 'name=%s\n' $name
printf 'cwd=%s\n' $_dir
if(! ~ $#backend 0) printf 'backend=%s\n' $backend
if(! ~ $#model 0) printf 'model=%s\n' $model
if(! ~ $#agent 0) printf 'agent=%s\n' $agent
} > $MOUNT/s/new
}
fn create_session {
if(! ~ $#name 0) {
write_new
id=$name
return
}
before=`{ls $MOUNT/s >[2]/dev/null}
write_new
deadline=`{expr `{date +%s} + 5}
while(test `{date +%s} -lt $deadline) {
for(d in `{ls $MOUNT/s >[2]/dev/null}) {
if(! ~ $d $before) {
id=$d
return
}
}
sleep 0.05
}
die 'timeout waiting for new session'
}
_picked=0
if(! ~ $#id_flag 0) {
if(! test -d $MOUNT/s/$id_flag) die 'session not found: '^$id_flag
id=$id_flag
printf 'session: %s (resumed)\n' $id >[1=2]
_picked=1
}
if(~ $_picked 0) if(~ $resume 1) {
id=`{load_session $_dir}
if(~ $#id 0) die 'no resumable session for '^$_dir
if(! test -d $MOUNT/s/$id) die 'no resumable session for '^$_dir
printf 'session: %s (resumed)\n' $id >[1=2]
_picked=1
}
if(~ $_picked 0) if(~ $new 1) {
create_session
save_session $_dir $id
printf 'session: %s\n' $id >[1=2]
_picked=1
}
if(~ $_picked 0) {
id=`{load_session $_dir}
if(! ~ $#id 0) if(test -d $MOUNT/s/$id) {
printf 'session: %s (resumed)\n' $id >[1=2]
_picked=1
}
if(~ $_picked 0) {
create_session
save_session $_dir $id
printf 'session: %s\n' $id >[1=2]
}
}
S=$MOUNT/s/$id
fn sigintr {
printf '\n' >[1=2]
echo stop > $S/ctl
exit 0
}
chat_off=0
if(test -f $S/chat) chat_off=`{wc -c < $S/chat}
if(! test -t 0) {
cat > $S/prompt || die 'submit failed'
exit 0
}
tail -n 40 $S/chat >[2]/dev/null
while(true) {
printf '> '
input=`{line}
if(! ~ $status 0) break
if(! ~ $#input 0) {
switch($"input) {
case /stop
echo stop > $S/ctl
case /kill
echo kill > $S/ctl
break
case /compact
echo compact > $S/ctl
case /clear
echo clear > $S/ctl
case /cwd
cat $S/cwd >[2]/dev/null
echo
case '/cwd *'
newcwd=`{printf '%s\n' $"input | sed 's,^/cwd ,,'}
printf 'cwd %s\n' $newcwd > $S/ctl
case /agent
cat $S/agent >[2]/dev/null
echo
case '/agent *'
printf '%s\n' `{printf '%s\n' $"input | sed 's,^/agent ,,'} > $S/agent
case /model
cat $S/model >[2]/dev/null
echo
case '/model *'
printf '%s\n' `{printf '%s\n' $"input | sed 's,^/model ,,'} > $S/model
case /backend
cat $S/backend >[2]/dev/null
echo
case '/backend *'
printf '%s\n' `{printf '%s\n' $"input | sed 's,^/backend ,,'} > $S/backend
case /models
cat $S/models >[2]/dev/null
case /backends
cat $MOUNT/backends >[2]/dev/null
case /usage
cat $S/usage >[2]/dev/null
case /ctxsz
cat $S/ctxsz >[2]/dev/null
case /mcp
cat $S/mcp >[2]/dev/null
case '/rn *'
newid=`{printf '%s\n' $"input | sed 's,^/rn ,,'}
printf 'rn %s\n' $newid > $S/ctl
id=$newid
S=$MOUNT/s/$id
save_session $_dir $id
case '/q *'
printf '%s\n' `{printf '%s\n' $"input | sed 's,^/q ,,'} > $S/fifo.in
printf 'queued\n' >[1=2]
case /*
printf 'unknown command: %s\n' `{printf '%s\n' $"input | awk '{print $1}'} >[1=2]
case *
printf '%s\n' $"input > $S/prompt || { printf 'submit failed\n' >[1=2] }
cat $S/statewait >/dev/null >[2]/dev/null
while(! is_idle) {
n=`{wc -c < $S/chat >[2]/dev/null}
if(~ $#n 0) n=0
if(test $n -gt $chat_off) {
tail -c +`{expr $chat_off + 1} $S/chat
chat_off=$n
}
sleep 0.05
}
n=`{wc -c < $S/chat >[2]/dev/null}
if(~ $#n 0) n=0
if(test $n -gt $chat_off) {
tail -c +`{expr $chat_off + 1} $S/chat
chat_off=$n
}
printf '\n'
}
}
}