#!/bin/bash # o — unified CLI for the ollie 9P namespace. # # Subcommands: # read Read a file from the active session/agent # ls [path] List namespace entries # ctl 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= OR eval \$(o env )" >&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= OR eval \$(o env $s )" >&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=)" >&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=)" >&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 [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 [args...]" >&2 echo " commands: stop, compact, kill, clear, model , backend , cwd " >&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="" line while true; do local prompt_str if [[ -z "$buf" ]]; then prompt_str=$'\033[1m>\033[0m ' else prompt_str=" " fi if ! IFS= read -e -r -p "$prompt_str" line; then # EOF — send remaining buffer if any if [[ -n "$buf" ]]; then history -s "$buf" 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 history -s "$line" 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 history -s "$buf" 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_readloop() { if [[ $# -lt 1 ]]; then echo "usage: o readloop " >&2 echo " read in a loop — re-reads after each return" >&2 exit 1 fi local file="$1"; shift local path path=$(agent_path) while true; do ollie-9p read "$path/$file" || break done } 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}" # Subcommand: o tui exit if [[ "${1:-}" == "exit" ]]; then tmux kill-session -t "$tmux_session" 2>/dev/null && echo "[killed $tmux_session]" || echo "no tui session: $tmux_session" return fi # 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" -c "$HOME" -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 readloop 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 [args...] commands: read Read a file from the active agent's namespace readloop Read in a loop (re-reads after each return) ls [path] List namespace entries (defaults to agent dir) ctl 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) tui exit Kill the tmux TUI session 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 [agent]) EOF } # ── Dispatch ───────────────────────────────────────────────────── if [[ $# -lt 1 ]]; then cmd_help exit 0 fi subcmd="$1"; shift case "$subcmd" in read) cmd_read "$@" ;; readloop) cmd_readloop "$@" ;; 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