ollie/data/scripts/o

346 lines
9.9 KiB
Bash
Executable File

#!/bin/bash
# o — unified CLI for the ollie 9P namespace.
#
# Subcommands:
# read <file> Read a file from the active session/agent
# ls [path] List namespace entries
# ctl <command> Send a control command (stop, compact, kill, ...)
# prompt Interactive REPL for submitting prompts
# tail Tail chat output from the active agent
# env Print resolved session/agent environment
#
# Environment:
# session Session name (set via 'eval $(o env)')
# agent Agent name
#
# If session/agent are not set and there is exactly one session, it is
# selected automatically. If multiple exist, lists them and exits.
set -euo pipefail
# ── Resolve session and agent ────────────────────────────────────
resolve_session() {
local s="${session:-}"
if [[ -n "$s" ]]; then
echo "$s"
return
fi
# Count sessions from idx
local idx
idx=$(ollie-9p read session/idx 2>/dev/null || true)
if [[ -z "$idx" ]]; then
return
fi
local names
names=$(echo "$idx" | cut -f1 | sort -u)
local count
count=$(echo "$names" | wc -l)
if [[ $count -eq 1 ]]; then
echo "$names"
else
echo "error: multiple sessions active, set one:" >&2
echo "$names" | sed 's/^/ /' >&2
echo "" >&2
echo " export session=<name> OR eval \$(o env <name>)" >&2
exit 1
fi
}
resolve_agent() {
local s="$1"
local a="${agent:-}"
if [[ -n "$a" ]]; then
echo "$a"
return
fi
if [[ -z "$s" ]]; then
return
fi
# List agents in session
local agents
agents=$(ollie-9p ls "session/$s/agent" 2>/dev/null | grep -v '^new$' || true)
if [[ -z "$agents" ]]; then
return
fi
local count
count=$(echo "$agents" | wc -l)
if [[ $count -eq 1 ]]; then
echo "$agents"
else
echo "error: multiple agents in session '$s', set one:" >&2
echo "$agents" | sed 's/^/ /' >&2
echo "" >&2
echo " export agent=<name> OR eval \$(o env $s <agent>)" >&2
exit 1
fi
}
require_session() {
local s
s=$(resolve_session)
if [[ -z "$s" ]]; then
echo "error: no session active (start one, or export session=<name>)" >&2
exit 1
fi
echo "$s"
}
require_agent() {
local s="$1"
local a
a=$(resolve_agent "$s")
if [[ -z "$a" ]]; then
echo "error: no agent set (export agent=<name>)" >&2
exit 1
fi
echo "$a"
}
agent_path() {
local s a
s=$(require_session)
a=$(require_agent "$s")
echo "session/$s/agent/$a"
}
# ── Subcommands ──────────────────────────────────────────────────
cmd_read() {
if [[ $# -lt 1 ]]; then
echo "usage: o read <file> [session [agent]]" >&2
exit 1
fi
local file="$1"; shift
[[ $# -ge 1 ]] && export session="${1}" && shift
[[ $# -ge 1 ]] && export agent="${1}" && shift
local path
path=$(agent_path)
ollie-9p read "$path/$file"
}
cmd_ls() {
local s
s=$(resolve_session) || true
if [[ $# -ge 1 ]]; then
local target="$1"; shift
if [[ "$target" == session/* || "$target" == elevate/* ]]; then
ollie-9p ls "$target"
elif [[ -n "$s" ]]; then
local a
a=$(resolve_agent "$s") || true
if [[ -n "$a" ]]; then
ollie-9p ls "session/$s/agent/$a/$target"
else
ollie-9p ls "session/$s/$target"
fi
else
ollie-9p ls "$target"
fi
elif [[ -n "$s" ]]; then
local a
a=$(resolve_agent "$s") || true
if [[ -n "$a" ]]; then
ollie-9p ls "session/$s/agent/$a"
else
ollie-9p ls "session/$s"
fi
else
ollie-9p ls session
fi
}
cmd_ctl() {
if [[ $# -lt 1 ]]; then
echo "usage: o ctl <command> [args...]" >&2
echo " commands: stop, compact, kill, clear, model <m>, backend <b>, cwd <d>" >&2
exit 1
fi
local path
path=$(agent_path)
echo "$*" | ollie-9p write "$path/ctl"
}
cmd_prompt() {
local path s a
path=$(agent_path)
s=$(require_session)
a=$(require_agent "$s")
echo "[$s/$a]"
echo "Enter prompts (blank line sends, Ctrl+D exits)."
echo "Slash commands: /stop /compact /kill /clear /model /backend /cwd /help"
echo ""
local buf=""
while true; do
if [[ -z "$buf" ]]; then
printf "\033[1m>\033[0m "
else
printf " "
fi
if ! IFS= read -r line; then
# EOF — send remaining buffer if any
if [[ -n "$buf" ]]; then
printf '%s\n' "$buf" | ollie-9p write "$path/prompt"
fi
printf "\n"
break
fi
# Slash commands (only when buffer is empty — first line)
if [[ -z "$buf" && "$line" == /* ]]; then
local cmd="${line#/}"
case "$cmd" in
stop) echo "stop" | ollie-9p write "$path/ctl"; echo "[stopped]" ;;
compact) echo "compact" | ollie-9p write "$path/ctl"; echo "[compacted]" ;;
kill) echo "kill" | ollie-9p write "session/$s/ctl"; echo "[session killed]"; break ;;
clear) echo "clear" | ollie-9p write "$path/ctl"; echo "[cleared]" ;;
model*) echo "$cmd" | ollie-9p write "$path/ctl"; echo "[${cmd}]" ;;
backend*) echo "$cmd" | ollie-9p write "$path/ctl"; echo "[${cmd}]" ;;
cwd*) echo "$cmd" | ollie-9p write "$path/ctl"; echo "[${cmd}]" ;;
help)
echo " /stop — interrupt running agent"
echo " /compact — compact context"
echo " /kill — kill the session"
echo " /clear — clear history"
echo " /model X — switch model"
echo " /backend X — switch backend"
echo " /cwd X — change working directory"
;;
*) echo "unknown command: /$cmd (try /help)" ;;
esac
continue
fi
if [[ -z "$line" && -n "$buf" ]]; then
# Blank line sends accumulated buffer
printf '%s\n' "$buf" | ollie-9p write "$path/prompt"
buf=""
elif [[ -z "$line" ]]; then
continue
else
if [[ -n "$buf" ]]; then
buf="$buf"$'\n'"$line"
else
buf="$line"
fi
fi
done
}
cmd_tail() {
local path
path=$(agent_path)
exec ollie-9p read "$path/chat"
}
cmd_env() {
# Accept optional positional args to override
[[ $# -ge 1 ]] && export session="${1}" && shift
[[ $# -ge 1 ]] && export agent="${1}" && shift
local s a
s=$(require_session)
a=$(require_agent "$s")
echo "export session=$s"
echo "export agent=$a"
}
cmd_stop() {
local path
path=$(agent_path)
echo "stop" | ollie-9p write "$path/ctl"
}
cmd_kill() {
local s
s=$(require_session)
echo "kill" | ollie-9p write "session/$s/ctl"
}
cmd_tui() {
local s a
s=$(require_session)
a=$(require_agent "$s")
local tmux_session="o-${s}"
# Kill existing tmux session if present
tmux kill-session -t "$tmux_session" 2>/dev/null || true
# Create new detached tmux session
# Pane 0: chat tail (left, large)
tmux new-session -d -s "$tmux_session" -x 200 -y 50 \
-e "session=$s" -e "agent=$a"
tmux send-keys -t "$tmux_session" "o tail" Enter
# Pane 1: state monitor (top-right)
tmux split-window -t "$tmux_session" -h -l 40 \
-e "session=$s" -e "agent=$a"
tmux send-keys -t "$tmux_session" "o read statewait" Enter
# Pane 2: spare shell (bottom-right, below state)
tmux split-window -t "$tmux_session" -v \
-e "session=$s" -e "agent=$a"
# Pane 3: prompt REPL (bottom, full width)
tmux split-window -t "$tmux_session:0.0" -v -l 8 \
-e "session=$s" -e "agent=$a"
tmux send-keys -t "$tmux_session" "o prompt" Enter
# Focus the prompt pane
tmux select-pane -t "$tmux_session:0.3"
# Attach
exec tmux attach -t "$tmux_session"
}
cmd_help() {
cat <<'EOF'
usage: o <command> [args...]
commands:
read <file> Read a file from the active agent's namespace
ls [path] List namespace entries (defaults to agent dir)
ctl <cmd> Send control command (stop, compact, kill, ...)
stop Interrupt the running agent
kill Kill the active session
prompt Interactive multi-line prompt REPL
tail Stream chat output
tui Launch tmux layout (chat + prompt + state + shell)
env [session] [agent] Print session/agent env vars (eval-able)
help Show this help
environment:
session Active session name
agent Active agent name
If only one session/agent exists, it is auto-selected.
If multiple exist, you must choose: eval $(o env <session> [agent])
EOF
}
# ── Dispatch ─────────────────────────────────────────────────────
if [[ $# -lt 1 ]]; then
cmd_help
exit 0
fi
subcmd="$1"; shift
case "$subcmd" in
read) cmd_read "$@" ;;
ls) cmd_ls "$@" ;;
ctl) cmd_ctl "$@" ;;
stop) cmd_stop "$@" ;;
kill) cmd_kill "$@" ;;
prompt) cmd_prompt "$@" ;;
tail) cmd_tail "$@" ;;
tui) cmd_tui "$@" ;;
env) cmd_env "$@" ;;
help|-h|--help) cmd_help ;;
*)
echo "unknown command: $subcmd" >&2
echo "run 'o help' for usage" >&2
exit 1
;;
esac