198 lines
6.6 KiB
Bash
Executable File
198 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# osh: interactive chat interface for ollie AI agents.
|
|
set -o history
|
|
|
|
MOUNT=${OLLIE:-$HOME/mnt/ollie}
|
|
LAST=$HOME/.config/ollie/last-session
|
|
|
|
# load_session <dir> → prints session ID for that dir, or nothing
|
|
load_session() {
|
|
awk -F'\t' -v d="$1" 'NF==2 && $1==d {print $2; exit}' "$LAST" 2>/dev/null
|
|
}
|
|
|
|
# save_session <dir> <id> → upserts {dir}\t{id} in $LAST
|
|
save_session() {
|
|
local dir="$1" id="$2" tmp
|
|
mkdir -p "$(dirname "$LAST")"
|
|
tmp=$(mktemp)
|
|
awk -F'\t' -v d="$dir" 'NF==2 && $1!=d' "$LAST" 2>/dev/null > "$tmp" || true
|
|
printf '%s\t%s\n' "$dir" "$id" >> "$tmp"
|
|
mv "$tmp" "$LAST"
|
|
}
|
|
|
|
cmd=${0##*/}
|
|
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
|
|
is_idle() { [ "$(tr -d '[:space:]' < "$S/state" 2>/dev/null)" = "idle" ]; }
|
|
drain() {
|
|
local n
|
|
n=$(wc -c < "$S/chat" 2>/dev/null || echo 0)
|
|
[ "$n" -gt "$chat_off" ] && tail -c "+$((chat_off+1))" "$S/chat" | head -c "$((n-chat_off))"
|
|
chat_off=$n
|
|
}
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
usage: $cmd [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
|
|
Ctrl-C interrupt running turn (or exit if idle)
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# Parse flags
|
|
new=0; resume=0; id_flag=; name=
|
|
backend=; model=; agent=; cwd=
|
|
while [ $# -gt 0 ]; do
|
|
case $1 in
|
|
-new) new=1 ;;
|
|
-resume) resume=1 ;;
|
|
-session) id_flag=$2; shift ;;
|
|
-name) name=$2; shift ;;
|
|
-backend) backend=$2; shift ;;
|
|
-model) model=$2; shift ;;
|
|
-agent) agent=$2; shift ;;
|
|
-cwd) cwd=$2; shift ;;
|
|
-help) usage ;;
|
|
*) die "unknown flag: $1" ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Write spec to s/new
|
|
write_new() {
|
|
{
|
|
[ -n "$name" ] && printf 'name=%s\n' "$name"
|
|
printf 'cwd=%s\n' "${cwd:-$(pwd)}"
|
|
[ -n "$backend" ] && printf 'backend=%s\n' "$backend"
|
|
[ -n "$model" ] && printf 'model=%s\n' "$model"
|
|
[ -n "$agent" ] && printf 'agent=%s\n' "$agent"
|
|
true
|
|
} > "$MOUNT/s/new" || die "write s/new"
|
|
}
|
|
|
|
# Create a fresh session, sets $id
|
|
create_session() {
|
|
local before d deadline
|
|
if [ -n "$name" ]; then
|
|
write_new
|
|
id=$name
|
|
else
|
|
before=$(ls "$MOUNT/s" 2>/dev/null)
|
|
write_new
|
|
deadline=$(($(date +%s) + 5))
|
|
while [ "$(date +%s)" -lt "$deadline" ]; do
|
|
for d in $(ls "$MOUNT/s" 2>/dev/null); do
|
|
printf '%s\n' "$before" | grep -qxF "$d" || { id=$d; return; }
|
|
done
|
|
sleep 0.05
|
|
done
|
|
die "timeout waiting for new session"
|
|
fi
|
|
}
|
|
|
|
# Resolve session
|
|
_dir=${cwd:-$(pwd)}
|
|
if [ -n "$id_flag" ]; then
|
|
[ -d "$MOUNT/s/$id_flag" ] || die "session not found: $id_flag"
|
|
id=$id_flag; printf 'session: %s (resumed)\n' "$id" >&2
|
|
elif [ "$resume" = 1 ]; then
|
|
id=$(load_session "$_dir")
|
|
[ -n "$id" ] && [ -d "$MOUNT/s/$id" ] || die "no resumable session for $_dir"
|
|
printf 'session: %s (resumed)\n' "$id" >&2
|
|
elif [ "$new" = 1 ]; then
|
|
create_session
|
|
save_session "$_dir" "$id"
|
|
printf 'session: %s\n' "$id" >&2
|
|
else
|
|
id=$(load_session "$_dir")
|
|
if [ -n "$id" ] && [ -d "$MOUNT/s/$id" ]; then
|
|
printf 'session: %s (resumed)\n' "$id" >&2
|
|
else
|
|
create_session
|
|
save_session "$_dir" "$id"
|
|
printf 'session: %s\n' "$id" >&2
|
|
fi
|
|
fi
|
|
|
|
S=$MOUNT/s/$id
|
|
chat_off=$(wc -c < "$S/chat" 2>/dev/null || echo 0)
|
|
|
|
# Export daemon environment to this shell.
|
|
while IFS='=' read -r k v; do
|
|
[ -n "$k" ] && export "$k=$v"
|
|
done < "$MOUNT/env" 2>/dev/null || true
|
|
|
|
trap '
|
|
[ "$(tr -d "[:space:]" < "$S/state" 2>/dev/null)" = "idle" ] && exit
|
|
printf "\n" >&2; printf "stop\n" > "$S/ctl"
|
|
' INT
|
|
|
|
while true; do
|
|
printf '> '
|
|
IFS= read -re input || break
|
|
[ -z "$input" ] && continue
|
|
history -s "$input"
|
|
case "$input" in
|
|
/stop) printf 'stop\n' > "$S/ctl"; continue ;;
|
|
/kill) printf 'kill\n' > "$S/ctl"; break ;;
|
|
/compact) printf 'compact\n' > "$S/ctl"; continue ;;
|
|
/clear) printf 'clear\n' > "$S/ctl"; continue ;;
|
|
"/rn "*) newid=${input#/rn }
|
|
printf 'rn %s\n' "$newid" > "$S/ctl"
|
|
id=$newid; S=$MOUNT/s/$id
|
|
save_session "$_dir" "$id"; continue ;;
|
|
/cwd) cat "$S/cwd" 2>/dev/null; printf '\n'; continue ;;
|
|
"/cwd "*) newcwd=${input#/cwd }
|
|
printf 'cwd %s\n' "$newcwd" > "$S/ctl"; continue ;;
|
|
/agent) cat "$S/agent" 2>/dev/null; printf '\n'; continue ;;
|
|
"/agent "*) printf '%s\n' "${input#/agent }" > "$S/agent"; continue ;;
|
|
/model) cat "$S/model" 2>/dev/null; printf '\n'; continue ;;
|
|
"/model "*) printf '%s\n' "${input#/model }" > "$S/model"; continue ;;
|
|
/backend) cat "$S/backend" 2>/dev/null; printf '\n'; continue ;;
|
|
"/backend "*) printf '%s\n' "${input#/backend }" > "$S/backend"; continue ;;
|
|
/models) cat "$S/models" 2>/dev/null; continue ;;
|
|
/backends) cat "$MOUNT/backends" 2>/dev/null; continue ;;
|
|
/usage) cat "$S/usage" 2>/dev/null; continue ;;
|
|
/ctxsz) cat "$S/ctxsz" 2>/dev/null; continue ;;
|
|
/mcp) cat "$S/mcp" 2>/dev/null; continue ;;
|
|
"/q "*) printf '%s\n' "${input#/q }" > "$S/enqueue"
|
|
printf 'queued\n' >&2; continue ;;
|
|
/*) printf 'unknown command: %s\n' "${input%% *}" >&2; continue ;;
|
|
*) printf '%s' "$input" > "$S/prompt" || { printf 'submit failed\n' >&2; continue; } ;;
|
|
esac
|
|
sleep 0.15
|
|
while true; do
|
|
drain
|
|
is_idle && { drain; printf '\n'; break; }
|
|
sleep 0.05
|
|
done
|
|
done
|