diff --git a/data/scripts/o b/data/scripts/o index 49331c1..7dd1ce6 100755 --- a/data/scripts/o +++ b/data/scripts/o @@ -9,17 +9,14 @@ # - No context set → path used as-is (root-relative) # # Subcommands: -# read Read a file +# read Read a file (-l to loop) # write [data] Write to a file (args or stdin) -# readloop Read in a loop (re-reads after each return) # ls [path] List namespace entries # ctl Send a control command (stop, compact, kill, ...) # prompt Interactive REPL for submitting prompts (requires agent) -# tail Tail chat output (requires agent) +# chatstream Stream chat output (requires agent) # tui Launch tmux TUI (requires agent) # env Print resolved session/agent environment -# stop Interrupt running agent (requires agent) -# kill Kill session (requires session) # # Environment: # session Session name (set via 'eval $(o env)') @@ -161,23 +158,29 @@ require_agent() { echo "$a" } -agent_path() { - local s a - s=$(require_session) || return 1 - a=$(require_agent "$s") || return 1 - echo "session/$s/agent/$a" -} - # ── Subcommands ────────────────────────────────────────────────── cmd_read() { + local loop=false + while [[ "${1:-}" == -* ]]; do + case "$1" in + -l|--loop) loop=true; shift ;; + *) break ;; + esac + done if [[ $# -lt 1 ]]; then - echo "usage: o read " >&2 + echo "usage: o read [-l] " >&2 exit 1 fi local path path=$(resolve_path "$1") || exit 1 - ollie-9p read "$path" + if $loop; then + while true; do + ollie-9p read "$path" || break + done + else + ollie-9p read "$path" + fi } cmd_write() { @@ -219,12 +222,8 @@ cmd_generate() { # One-shot generation via /generate (no session/agent needed) # Usage: o generate "prompt" # echo "prompt" | o generate - # o generate -1 "prompt" (same as above) local prompt="" - # Handle -1 flag (ignored, for compatibility) - [[ "${1:-}" == "-1" ]] && shift - if [[ $# -gt 0 ]]; then prompt="$*" else @@ -270,28 +269,40 @@ cmd_route() { printf '%s' "$task" | ollie-9p rdwr route } -cmd_readloop() { - if [[ $# -lt 1 ]]; then - echo "usage: o readloop " >&2 - exit 1 - fi - local file="$1"; shift - local path - path=$(resolve_path "$file") || exit 1 - while true; do - ollie-9p read "$path" || break - done -} - cmd_ctl() { if [[ $# -lt 1 ]]; then - echo "usage: o ctl [args...]" >&2 - echo " commands: stop, compact, kill, clear, model , backend , cwd " >&2 + echo "usage: o ctl [session] [agent]" >&2 + echo " With agent: stop, compact, clear, model , backend , cwd " >&2 + echo " Session-only: kill, pause, resume" >&2 exit 1 fi - local path - path=$(agent_path) || exit 1 - echo "$*" | ollie-9p write "$path/ctl" + local cmd="$1"; shift + + # Resolve context from remaining args or env + local s="${1:-${session:-}}" + local a="${2:-${agent:-}}" + + if [[ -z "$s" ]]; then + echo "error: no session set" >&2 + echo " export session= or o ctl [agent]" >&2 + exit 1 + fi + + # Session-level commands (no agent needed) + case "$cmd" in + kill|pause|resume) + echo "$cmd" | ollie-9p write "session/$s/ctl" + return + ;; + esac + + # Agent-level commands + if [[ -z "$a" ]]; then + echo "error: no agent set" >&2 + echo " export agent= or o ctl " >&2 + exit 1 + fi + echo "$cmd" | ollie-9p write "session/$s/agent/$a/ctl" } cmd_prompt() { @@ -314,9 +325,6 @@ cmd_prompt() { local buf="" line - # Enable bracketed paste mode - terminal sends \e[200~ before and \e[201~ after paste - bind 'set enable-bracketed-paste on' 2>/dev/null - while true; do local prompt_str if [[ -z "$buf" ]]; then @@ -365,7 +373,7 @@ cmd_prompt() { echo " /backend X — switch backend" echo " /cwd X — change working directory" echo " /quit /q — kill the tmux TUI" - echo " /i — interrupt running agent" + echo " /i — inject prompt mid-turn" ;; *) echo "unknown command: /$cmd (try /help)" ;; esac @@ -395,20 +403,6 @@ cmd_prompt() { done } -cmd_chatstream() { - local ctx s a - ctx=$(resolve_context "$@") || exit 1 - s="${ctx%%|*}" - a="${ctx##*|}" - if [[ -z "$s" || -z "$a" ]]; then - echo "error: session and agent are required" >&2 - echo " usage: o chatstream " >&2 - echo " or: export session= agent=" >&2 - exit 1 - fi - exec ollie-9p read "session/$s/agent/$a/chat" -} - cmd_log() { local lines=1000 local ctx s a @@ -449,31 +443,6 @@ cmd_env() { fi } -cmd_stop() { - local ctx s a - ctx=$(resolve_context "$@") || exit 1 - s="${ctx%%|*}" - a="${ctx##*|}" - if [[ -z "$s" || -z "$a" ]]; then - echo "error: session and agent are required" >&2 - echo " usage: o stop " >&2 - echo " or: export session= agent=" >&2 - exit 1 - fi - echo "stop" | ollie-9p write "session/$s/agent/$a/ctl" -} - -cmd_kill() { - local s="${1:-${session:-}}" - if [[ -z "$s" ]]; then - echo "error: no session set" >&2 - echo " usage: o kill " >&2 - echo " or: export session=" >&2 - exit 1 - fi - echo "kill" | ollie-9p write "session/$s/ctl" -} - cmd_tui() { local tmux_session="${session:-}" local s a @@ -523,7 +492,7 @@ cmd_tui() { # Pane 0: chat (top, 80%) - show last 1000 lines of log, then tail chat tmux -CC new-session -d -s "$tmux_session" -c "$HOME" \ -e "session=$s" -e "agent=$a" - tmux send-keys -t "$tmux_session" "o log -n 1000 | grep -v '\\[\\[\\[.*\\]\\]\\]' | grep -v '^\`\`\`'; o chatstream" Enter + tmux send-keys -t "$tmux_session" "o log -n 1000 | grep -v '\\[\\[\\[.*\\]\\]\\]' | grep -v '^\`\`\`'; o read chat" Enter # Pane 1: prompt REPL (bottom, 20% height) tmux split-window -t "$tmux_session" -v -l 20% \ @@ -544,10 +513,12 @@ cmd_tui() { paused) bg="colour245" ;; # gray *) bg="default" ;; esac - tmux set-option -t "$tmux_session" status-right-style "fg=default,bg=$bg" - tmux set-option -t "$tmux_session" status-right "$state | %H:%M" + tmux set-option -t "$tmux_session" status-right-style "fg=default,bg=$bg" 2>/dev/null + tmux set-option -t "$tmux_session" status-right "$state | %H:%M" 2>/dev/null done done & + local bg_pid=$! + trap "kill $bg_pid 2>/dev/null" EXIT # Focus the prompt pane tmux select-pane -t "$tmux_session:0.1" @@ -561,25 +532,21 @@ usage: o [args...] Path resolution: - Root paths (session/*, agents, models, etc.) → used as-is - - Agent files (chat, state, prompt, etc.) → require $session + $agent + - Agent files (chat, prompt, cfg, etc.) → require $session + $agent - Session files (env, ctl, name, etc.) → require $session Commands: - read Read a file + read [-l] Read a file (-l: loop, re-read after each return) write [data] Write to a file (args or stdin) - readloop Read in a loop (re-reads after return) ls [path] List namespace entries generate One-shot generation (no session needed) complete Code completion (no session needed) route Route task to best model (no session needed) - ctl Send control command to agent - stop Interrupt running agent - kill Kill active session + ctl [s] [a] Send control command (session or agent level) prompt Interactive multi-line prompt REPL - chatstream Stream chat output log [-n N] Show last N lines of agent log (default 1000) tui Launch tmux layout tui exit Kill tmux TUI session @@ -595,12 +562,13 @@ Examples: o ls # list at current context level o ls session # list all sessions o read models # read root-level file + o read -l statewait # block-loop on state changes + o read chat # stream chat (filtered text) + o read chat.raw # stream chat (full markup) o generate "explain monads" # one-shot, no session - o complete '{"file":"main.go","prefix":"func "}' # code completion - o route "implement login" # route to best model + o ctl stop # stop agent (uses $session/$agent) + o ctl kill mysession # kill session by name export session=foo agent=bar # set context - o read chat # read agent's chat - o readloop statewait # blocks until agent state changes EOF } @@ -615,16 +583,12 @@ subcmd="$1"; shift case "$subcmd" in read) cmd_read "$@" ;; write) cmd_write "$@" ;; - readloop) cmd_readloop "$@" ;; ls) cmd_ls "$@" ;; generate) cmd_generate "$@" ;; complete) cmd_complete "$@" ;; route) cmd_route "$@" ;; ctl) cmd_ctl "$@" ;; - stop) cmd_stop "$@" ;; - kill) cmd_kill "$@" ;; prompt) cmd_prompt "$@" ;; - chatstream) cmd_chatstream "$@" ;; log) cmd_log "$@" ;; tui) cmd_tui "$@" ;; env) cmd_env "$@" ;;