From 14a52236c8b84de01bbd40c4714e98ea8546f572 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Mon, 10 Aug 2026 13:37:29 +0200 Subject: [PATCH] o: hierarchical context selectors Refactor o CLI to use explicit context selectors: - o Root level - o session Session level - o agent Agent level Environment variables $session and $agent can substitute for positional args for backwards compatibility. Update acme scripts to use new interface: o agent $OLLIE_SESSION $OLLIE_AGENT prompt --- data/scripts/acme/AddTests | 2 +- data/scripts/acme/AskFile | 2 +- data/scripts/acme/Document | 2 +- data/scripts/acme/Explain | 2 +- data/scripts/acme/Fix | 2 +- data/scripts/acme/Refactor | 2 +- data/scripts/acme/SendVerbatim | 2 +- data/scripts/acme/acme-ollie-ensure | 9 +- data/scripts/o | 578 +++++++++++----------------- 9 files changed, 235 insertions(+), 366 deletions(-) diff --git a/data/scripts/acme/AddTests b/data/scripts/acme/AddTests index 2bde558..19d605f 100755 --- a/data/scripts/acme/AddTests +++ b/data/scripts/acme/AddTests @@ -38,4 +38,4 @@ prompt="Write unit tests for this code${location}: $selection \`\`\`" -echo "$prompt" | o prompt +echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt diff --git a/data/scripts/acme/AskFile b/data/scripts/acme/AskFile index 8efe10c..eb636bd 100755 --- a/data/scripts/acme/AskFile +++ b/data/scripts/acme/AskFile @@ -24,4 +24,4 @@ eval $(acme-ollie-ensure) # Build and send the prompt prompt="Describe and explain this file: $filename" -echo "$prompt" | o prompt +echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt diff --git a/data/scripts/acme/Document b/data/scripts/acme/Document index 86096e5..76cb6d9 100755 --- a/data/scripts/acme/Document +++ b/data/scripts/acme/Document @@ -38,4 +38,4 @@ prompt="Add documentation comments to this code${location}: $selection \`\`\`" -echo "$prompt" | o prompt +echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt diff --git a/data/scripts/acme/Explain b/data/scripts/acme/Explain index 0593f2c..566537b 100755 --- a/data/scripts/acme/Explain +++ b/data/scripts/acme/Explain @@ -38,4 +38,4 @@ prompt="Explain this code${context}: $selection \`\`\`" -echo "$prompt" | o prompt +echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt diff --git a/data/scripts/acme/Fix b/data/scripts/acme/Fix index 13187c7..d3dfa2e 100755 --- a/data/scripts/acme/Fix +++ b/data/scripts/acme/Fix @@ -38,4 +38,4 @@ prompt="Fix this code${location}: $selection \`\`\`" -echo "$prompt" | o prompt +echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt diff --git a/data/scripts/acme/Refactor b/data/scripts/acme/Refactor index 967821e..931fa9e 100755 --- a/data/scripts/acme/Refactor +++ b/data/scripts/acme/Refactor @@ -38,4 +38,4 @@ prompt="Refactor this code${location}: $selection \`\`\`" -echo "$prompt" | o prompt +echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt diff --git a/data/scripts/acme/SendVerbatim b/data/scripts/acme/SendVerbatim index 3becbfd..1b17b29 100755 --- a/data/scripts/acme/SendVerbatim +++ b/data/scripts/acme/SendVerbatim @@ -62,4 +62,4 @@ prompt="\`\`\`${lang} $selection \`\`\`" -echo "$prompt" | o prompt +echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt diff --git a/data/scripts/acme/acme-ollie-ensure b/data/scripts/acme/acme-ollie-ensure index 9f9ec97..8531b11 100755 --- a/data/scripts/acme/acme-ollie-ensure +++ b/data/scripts/acme/acme-ollie-ensure @@ -2,10 +2,10 @@ # acme-ollie-ensure - Ensure acme session and agent exist # # Creates session "acme" if needed, creates/selects agent based on cwd hash. -# Outputs the agent path for use by other scripts. +# Outputs the session and agent names for use by other scripts. # # Usage: eval $(acme-ollie-ensure) -# Then use $OLLIE_AGENT_PATH +# Then use $OLLIE_SESSION and $OLLIE_AGENT set -euo pipefail @@ -45,6 +45,5 @@ else fi # Output for eval -echo "export session=$SESSION" -echo "export agent=$AGENT" -echo "export OLLIE_AGENT_PATH=session/$SESSION/agent/$AGENT" +echo "export OLLIE_SESSION=$SESSION" +echo "export OLLIE_AGENT=$AGENT" diff --git a/data/scripts/o b/data/scripts/o index 145efed..d691e49 100755 --- a/data/scripts/o +++ b/data/scripts/o @@ -1,165 +1,39 @@ #!/bin/bash # o — unified CLI for the ollie 9P namespace. # -# Path resolution: -# Paths are resolved based on context: -# - Absolute paths (starting with session/, agents, models, etc.) → used as-is -# - Relative paths with $session set but no $agent → session/$session/ -# - Relative paths with both $session and $agent → session/$session/agent/$agent/ -# - No context set → path used as-is (root-relative) +# Hierarchical access: +# o [args] Root level operations +# o session [args] Session-level operations +# o agent [args] Agent-level operations +# +# Environment variables ($session, $agent) can substitute for positional args. # # Subcommands: # read Read a file (-l to loop) # write [data] Write to a file (args or stdin) # ls [path] List namespace entries -# new [agent] [cwd] Create a new session or agent -# ctl Send a control command (stop, compact, kill, ...) -# prompt Interactive REPL for submitting prompts (requires agent) -# chatstream Stream chat output (requires agent) -# tui Launch tmux TUI (requires agent) +# new [name] [cwd] Create session or agent (context-dependent) +# ctl Send a control command +# prompt Interactive REPL for submitting prompts (agent-level) +# tui Launch tmux TUI (agent-level) +# generate One-shot generation (root-level only) # env Print resolved session/agent environment # -# Environment: -# session Session name (set via 'eval $(o env)') -# agent Agent name +# Examples: +# o ls List root (sessions, models, etc.) +# o session myproj ls List session contents +# o session myproj new coding Create agent 'coding' in 'myproj' +# o agent myproj coding prompt Interactive prompt +# o agent myproj coding read chat Read chat output set -euo pipefail -# ── Path classification ───────────────────────────────────────── +# ── Globals set by dispatch ───────────────────────────────────── +PREFIX="" # Path prefix based on context +CTX_SESSION="" # Session name if in session/agent context +CTX_AGENT="" # Agent name if in agent context -# Root-level entries (no context needed) -ROOT_ENTRIES="session bypass agents backends models help ctl eventwait generate aliases tools" - -# Session-level files (exist directly under session/{name}/) -SESSION_FILES="env paused connected ctl name id bypass" - -# Agent-level files (exist under session/{name}/agent/{name}/) -AGENT_FILES="prompt fifo chat chat.raw statewait log plan cfg ctl stats name id proc" - -is_root_path() { - local path="$1" - local first="${path%%/*}" - for entry in $ROOT_ENTRIES; do - [[ "$first" == "$entry" ]] && return 0 - done - return 1 -} - -is_session_file() { - local name="$1" - for f in $SESSION_FILES; do - [[ "$name" == "$f" ]] && return 0 - done - [[ "$name" == "agent" || "$name" == agent/* ]] && return 0 - return 1 -} - -is_agent_file() { - local name="$1" - local base="${name%%/*}" - for f in $AGENT_FILES; do - [[ "$base" == "$f" ]] && return 0 - done - return 1 -} - -# ── Path resolution with validation ───────────────────────────── - -# Resolve a path based on current context, with meaningful errors. -# Returns 0 on success (path on stdout), 1 on error (message on stderr). -resolve_path() { - local path="$1" - - # Root-level paths used as-is - if is_root_path "$path"; then - echo "$path" - return 0 - fi - - # Get context - local s="${session:-}" - local a="${agent:-}" - - # Determine what level this path requires - local base="${path%%/*}" - - # Check if it's an agent-level file - if is_agent_file "$base"; then - if [[ -z "$s" ]]; then - echo "error: '$path' requires session context" >&2 - echo " export session= or use full path: session//agent//$path" >&2 - return 1 - fi - if [[ -z "$a" ]]; then - echo "error: '$path' requires agent context" >&2 - echo " export agent= or use full path: session/$s/agent//$path" >&2 - return 1 - fi - echo "session/$s/agent/$a/$path" - return 0 - fi - - # Check if it's a session-level file/dir - if is_session_file "$base"; then - if [[ -z "$s" ]]; then - echo "error: '$path' requires session context" >&2 - echo " export session= or use full path: session//$path" >&2 - return 1 - fi - echo "session/$s/$path" - return 0 - fi - - # Unknown relative path — try with whatever context we have - if [[ -n "$s" && -n "$a" ]]; then - echo "session/$s/agent/$a/$path" - elif [[ -n "$s" ]]; then - echo "session/$s/$path" - else - echo "$path" - fi - return 0 -} - -# ── Context requirements for specific commands ────────────────── - -# Resolve session and agent from positional args or env vars. -# Usage: resolve_context "$@" → returns (s, a) by modifying caller's vars. -# Pass "$@" from the calling command; it shifts positional args. -resolve_context() { - local _s _a - _s="${1:-${session:-}}" - if [[ -n "${1+set}" ]]; then - shift - _a="${1:-${agent:-}}" - else - _a="${agent:-}" - fi - echo "$_s|$_a" -} - -require_session() { - local s="${session:-}" - if [[ -z "$s" ]]; then - echo "error: no session set" >&2 - echo " export session=" >&2 - return 1 - fi - echo "$s" -} - -require_agent() { - local s="$1" - local a="${agent:-}" - if [[ -z "$a" ]]; then - echo "error: no agent set" >&2 - echo " export agent=" >&2 - return 1 - fi - echo "$a" -} - -# ── Subcommands ────────────────────────────────────────────────── +# ── Subcommands ───────────────────────────────────────────────── cmd_read() { local loop=false @@ -173,8 +47,7 @@ cmd_read() { echo "usage: o read [-l] " >&2 exit 1 fi - local path - path=$(resolve_path "$1") || exit 1 + local path="${PREFIX}${PREFIX:+/}$1" if $loop; then while true; do ollie-9p read "$path" || break @@ -190,8 +63,7 @@ cmd_write() { exit 1 fi local file="$1"; shift - local path - path=$(resolve_path "$file") || exit 1 + local path="${PREFIX}${PREFIX:+/}$file" if [[ $# -gt 0 ]]; then printf '%s\n' "$*" | ollie-9p write "$path" else @@ -200,95 +72,74 @@ cmd_write() { } cmd_ls() { + local path="${PREFIX}" if [[ $# -ge 1 ]]; then - local path - path=$(resolve_path "$1") || exit 1 - ollie-9p ls "$path" - else - # No arg: list based on context level - local s="${session:-}" - local a="${agent:-}" - - if [[ -n "$s" && -n "$a" ]]; then - ollie-9p ls "session/$s/agent/$a" - elif [[ -n "$s" ]]; then - ollie-9p ls "session/$s" - else - ollie-9p ls "" - fi + path="${PREFIX}${PREFIX:+/}$1" fi + ollie-9p ls "$path" } cmd_generate() { - # One-shot generation via /generate (no session/agent needed) - # Usage: o generate "prompt" - # echo "prompt" | o generate + # One-shot generation via /generate (root-level only) + if [[ -n "$PREFIX" ]]; then + echo "error: generate is a root-level command" >&2 + echo " usage: o generate " >&2 + exit 1 + fi local prompt="" - if [[ $# -gt 0 ]]; then prompt="$*" else prompt=$(cat) fi - [[ -z "$prompt" ]] && { echo "usage: o generate " >&2; exit 1; } - printf '%s' "$prompt" | ollie-9p rdwr generate } cmd_ctl() { if [[ $# -lt 1 ]]; then echo "usage: o ctl [args...]" >&2 - echo " Agent: stop, compact, clear, inject, agent, model, cwd, name" >&2 - echo " Session: kill, pause, resume" >&2 + if [[ -n "$CTX_AGENT" ]]; then + echo " Agent: stop, compact, clear, inject, agent, model, cwd, name" >&2 + elif [[ -n "$CTX_SESSION" ]]; then + echo " Session: kill, pause, resume" >&2 + else + echo " (requires session or agent context)" >&2 + fi exit 1 fi - local s="${session:-}" - local a="${agent:-}" - - if [[ -z "$s" ]]; then - echo "error: no session set (export session=)" >&2 + if [[ -z "$PREFIX" ]]; then + echo "error: ctl requires session or agent context" >&2 + echo " usage: o session ctl " >&2 + echo " or: o agent ctl " >&2 exit 1 fi - # Session-level commands (no agent needed) - case "$1" in - kill|pause|resume) - echo "$*" | ollie-9p write "session/$s/ctl" - return - ;; - esac - - # Agent-level commands (rdwr: write command, read response) - if [[ -z "$a" ]]; then - echo "error: no agent set (export agent=)" >&2 - exit 1 + if [[ -n "$CTX_AGENT" ]]; then + # Agent-level: rdwr to get response + printf '%s' "$*" | ollie-9p rdwr "${PREFIX}/ctl" + else + # Session-level: write only + echo "$*" | ollie-9p write "${PREFIX}/ctl" fi - printf '%s' "$*" | ollie-9p rdwr "session/$s/agent/$a/ctl" } cmd_prompt() { - 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 prompt " >&2 - echo " or: export session= agent=" >&2 + if [[ -z "$CTX_AGENT" ]]; then + echo "error: prompt requires agent context" >&2 + echo " usage: o agent prompt" >&2 exit 1 fi - local path="session/$s/agent/$a" # If stdin is not a terminal, read it all and send as a single prompt if [[ ! -t 0 ]]; then - cat | ollie-9p write "$path/prompt" + cat | ollie-9p write "${PREFIX}/prompt" return fi # Interactive REPL mode - echo "[$s/$a]" + echo "[$CTX_SESSION/$CTX_AGENT]" echo "Type . on a blank line to send, Ctrl+D to exit." echo "/ prefix runs ctl: /stop, /compact, /model X, /inject X" echo "!q exits the TUI." @@ -307,7 +158,7 @@ cmd_prompt() { if ! IFS= read -r line; then if [[ -n "$buf" ]]; then history -s "$buf" - printf '%s\n' "$buf" | ollie-9p write "$path/prompt" + printf '%s\n' "$buf" | ollie-9p write "${PREFIX}/prompt" fi printf "\n" break @@ -319,28 +170,26 @@ cmd_prompt() { done if [[ -z "$buf" && "$line" == /* ]]; then history -s "$line" - o ctl "${line#/}" || true + cmd_ctl "${line#/}" || true continue fi if [[ -z "$buf" && "$line" == !* ]]; then history -s "$line" local bang="${line#!}" case "$bang" in - q|quit) tmux kill-session -t "o-${s}" 2>/dev/null; exit 0 ;; + q|quit) tmux kill-session -t "o-${CTX_SESSION}" 2>/dev/null; exit 0 ;; esac continue fi if [[ "$line" == "." ]]; then - # Send prompt on "." alone if [[ -n "$buf" ]]; then history -s "$buf" - printf '%s\n' "$buf" | ollie-9p write "$path/prompt" + printf '%s\n' "$buf" | ollie-9p write "${PREFIX}/prompt" buf="" fi continue fi if [[ -z "$line" && -n "$buf" ]]; then - # Empty line during multi-line input: append newline buf="$buf"$'\n' elif [[ -z "$line" ]]; then continue @@ -356,9 +205,6 @@ cmd_prompt() { cmd_log() { local lines=1000 - local ctx s a - - # Parse -n flag for line count while [[ $# -gt 0 && "$1" == -* ]]; do case "$1" in -n) shift; lines="${1:-1000}"; shift ;; @@ -367,125 +213,82 @@ cmd_log() { esac done - 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 log [-n lines] [session] [agent]" >&2 - echo " or: export session= agent=" >&2 + if [[ -z "$CTX_AGENT" ]]; then + echo "error: log requires agent context" >&2 + echo " usage: o agent log [-n N]" >&2 exit 1 fi - ollie-9p read "session/$s/agent/$a/log" | tail -n "$lines" -} - -cmd_env() { - # Accept optional positional args to set context - if [[ $# -ge 1 ]]; then - echo "export session=$1" - shift - elif [[ -n "${session:-}" ]]; then - echo "export session=$session" - fi - if [[ $# -ge 1 ]]; then - echo "export agent=$1" - elif [[ -n "${agent:-}" ]]; then - echo "export agent=$agent" - fi + ollie-9p read "${PREFIX}/log" | tail -n "$lines" } cmd_new() { - if [[ $# -lt 1 ]]; then - echo "usage: o new [agent] [cwd]" >&2 - echo " o new myproject Create a new session named 'myproject'" >&2 - echo " o new myproject coding /path Create agent 'coding' in session 'myproject'" >&2 + if [[ -n "$CTX_AGENT" ]]; then + echo "error: cannot create nested agents" >&2 exit 1 fi - local session_name="$1" - shift - - if [[ $# -eq 0 ]]; then - # Create new session - printf 'name=%s\ncwd=%s\n' "$session_name" "$PWD" | ollie-9p write "session/new" - echo "created session: $session_name" - else - # Create new agent in existing session + if [[ -n "$CTX_SESSION" ]]; then + # Create agent in current session + if [[ $# -lt 1 ]]; then + echo "usage: o session new [cwd]" >&2 + exit 1 + fi local agent_name="$1" local agent_cwd="${2:-$PWD}" - printf 'name=%s\ncwd=%s\n' "$agent_name" "$agent_cwd" | ollie-9p write "session/$session_name/agent/new" - echo "created agent: $session_name/$agent_name" + printf 'name=%s\ncwd=%s\n' "$agent_name" "$agent_cwd" | ollie-9p write "${PREFIX}/agent/new" + echo "created agent: $CTX_SESSION/$agent_name" + else + # Create session at root + if [[ $# -lt 1 ]]; then + echo "usage: o new [cwd]" >&2 + exit 1 + fi + local session_name="$1" + local session_cwd="${2:-$PWD}" + printf 'name=%s\ncwd=%s\n' "$session_name" "$session_cwd" | ollie-9p write "session/new" + echo "created session: $session_name" fi } cmd_tui() { - local tmux_session="${session:-}" - local s a + if [[ -z "$CTX_AGENT" ]]; then + echo "error: tui requires agent context" >&2 + echo " usage: o agent tui" >&2 + exit 1 + fi - # Check for subcommands first + local s="$CTX_SESSION" + local a="$CTX_AGENT" + + # Check for subcommands if [[ "${1:-}" == "exit" ]]; then - if [[ -z "$tmux_session" ]]; then - echo "error: no session context" >&2 - exit 1 - fi - tmux kill-session -t "o-${tmux_session}" 2>/dev/null && echo "[killed o-${tmux_session}]" || echo "no tui session: o-${tmux_session}" + tmux kill-session -t "o-${s}" 2>/dev/null && echo "[killed o-${s}]" || echo "no tui session: o-${s}" return fi - # Accept optional positional args: o tui [session] [agent] - local ctx - ctx=$(resolve_context "$@") - s="${ctx%%|*}" - a="${ctx##*|}" - - if [[ -z "$s" ]]; then - echo "error: no session set" >&2 - echo " usage: o tui [agent]" >&2 - echo " or: export session=" >&2 - exit 1 - fi - if [[ -z "$a" ]]; then - echo "error: no agent set" >&2 - echo " usage: o tui " >&2 - echo " or: export agent=" >&2 - exit 1 - fi - - tmux_session="o-${s}" - + local tmux_session="o-${s}" tmux kill-session -t "$tmux_session" 2>/dev/null || true - # Layout: - # ┌──────────────────────────────────┐ - # │ chat │ - # │ │ - # ├──────────────────────────────────┤ - # │ prompt (20%) │ - # └──────────────────────────────────┘ - # State shown in tmux status-right. - - # Pane 0: chat (top, 80%) - show last 1000 lines of log, then tail chat + # Layout: chat (top 80%), prompt (bottom 20%) 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 read chat" Enter + tmux send-keys -t "$tmux_session" "o agent $s $a log -n 1000 | grep -v '\\[\\[\\[.*\\]\\]\\]' | grep -v '^\`\`\`'; o agent $s $a read chat" Enter - # Pane 1: prompt REPL (bottom, 20% height) tmux split-window -t "$tmux_session" -v -l 20% \ -e "session=$s" -e "agent=$a" - tmux send-keys -t "$tmux_session" "clear; o prompt" Enter + tmux send-keys -t "$tmux_session" "clear; o agent $s $a prompt" Enter - # Show agent state in tmux status bar (live updates via statewait) tmux set-option -t "$tmux_session" status-right "starting... | %H:%M" tmux set-option -t "$tmux_session" status-right-length 64 - # Background: loops on statewait, updates status bar instantly on each change + while true; do - ollie-9p read "session/$s/agent/$a/statewait" 2>/dev/null | while read -r state; do + ollie-9p read "${PREFIX}/statewait" 2>/dev/null | while read -r state; do bg="" case "$state" in - idle) bg="colour70" ;; # green - calling*) bg="colour214" ;; # orange - thinking) bg="colour33" ;; # blue - paused) bg="colour245" ;; # gray + idle) bg="colour70" ;; + calling*) bg="colour214" ;; + thinking) bg="colour33" ;; + paused) bg="colour245" ;; *) bg="default" ;; esac tmux set-option -t "$tmux_session" status-right-style "fg=default,bg=$bg" 2>/dev/null @@ -495,84 +298,151 @@ cmd_tui() { local bg_pid=$! trap "kill $bg_pid 2>/dev/null" EXIT - # Focus the prompt pane tmux select-pane -t "$tmux_session:0.1" - exec tmux attach -t "$tmux_session" } +cmd_env() { + # Print export statements for current context + [[ -n "$CTX_SESSION" ]] && echo "export session=$CTX_SESSION" + [[ -n "$CTX_AGENT" ]] && echo "export agent=$CTX_AGENT" +} + cmd_help() { cat <<'EOF' -usage: o [args...] +usage: o [context] [args...] -Path resolution: - - Root paths (session/*, agents, models, etc.) → used as-is - - Agent files (chat, prompt, cfg, etc.) → require $session + $agent - - Session files (env, ctl, name, etc.) → require $session +Context selectors: + o Root level + o session Session level + o agent Agent level -Commands: - read [-l] Read a file (-l: loop, re-read after each return) - write [data] Write to a file (args or stdin) - ls [path] List namespace entries +Environment variables $session and $agent can substitute for positional args: + export session=foo agent=bar + o agent prompt # equivalent to: o agent foo bar prompt - new Create a new session - new [cwd] Create an agent in a session +Commands (availability depends on context): - generate One-shot generation (no session needed) + Root level: + ls List sessions, models, etc. + new [cwd] Create a new session + generate One-shot generation (no session needed) - ctl [args] Send control command (rdwr: gets response) + Session level (o session ...): + ls List session contents (agents, config) + new [cwd] Create a new agent + ctl Session control (kill, pause, resume) + read/write Access session files - prompt Interactive multi-line prompt REPL - log [-n N] Show last N lines of agent log (default 1000) - tui Launch tmux layout - tui exit Kill tmux TUI session + Agent level (o agent ...): + ls List agent files + ctl Agent control (stop, compact, model, etc.) + prompt Interactive multi-line prompt REPL + log [-n N] Show last N lines of agent log + tui Launch tmux layout + read/write Access agent files (chat, cfg, etc.) - env [session] [agent] Print export statements for context - help Show this help - -Environment: - session Session name - agent Agent name + Common: + read [-l] Read a file (-l: loop) + write [data] Write to a file + env Print export statements for context + help Show this help 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 new myproject # create session 'myproject' in current dir - o new myproject coding # create agent 'coding' in 'myproject' - o generate "explain monads" # one-shot, no session - o ctl stop # stop agent (uses $session/$agent) - o ctl kill mysession # kill session by name - export session=foo agent=bar # set context + o ls # list root (sessions, models) + o new myproj # create session 'myproj' + o session myproj ls # list session contents + o session myproj new coding # create agent 'coding' + o agent myproj coding prompt # interactive prompt + o agent myproj coding read chat # read chat stream + o agent myproj coding tui # launch tmux TUI + o generate "explain monads" # one-shot, no session + + # With environment: + export session=myproj agent=coding + o agent prompt # uses env vars EOF } -# ── Dispatch ───────────────────────────────────────────────────── +# ── Dispatch ──────────────────────────────────────────────────── +dispatch_cmd() { + local cmd="$1"; shift + case "$cmd" in + read) cmd_read "$@" ;; + write) cmd_write "$@" ;; + ls) cmd_ls "$@" ;; + generate) cmd_generate "$@" ;; + ctl) cmd_ctl "$@" ;; + prompt) cmd_prompt "$@" ;; + log) cmd_log "$@" ;; + new) cmd_new "$@" ;; + tui) cmd_tui "$@" ;; + env) cmd_env "$@" ;; + help|-h|--help) cmd_help ;; + *) + echo "unknown command: $cmd" >&2 + echo "run 'o help' for usage" >&2 + exit 1 + ;; + esac +} + +# Main dispatch: check for context selectors if [[ $# -lt 1 ]]; then cmd_help exit 0 fi -subcmd="$1"; shift -case "$subcmd" in - read) cmd_read "$@" ;; - write) cmd_write "$@" ;; - ls) cmd_ls "$@" ;; - generate) cmd_generate "$@" ;; - ctl) cmd_ctl "$@" ;; - prompt) cmd_prompt "$@" ;; - log) cmd_log "$@" ;; - new) cmd_new "$@" ;; - tui) cmd_tui "$@" ;; - env) cmd_env "$@" ;; - help|-h|--help) cmd_help ;; +case "$1" in + session) + shift + # Get session name from arg or env + CTX_SESSION="${1:-${session:-}}" + if [[ -n "${1:-}" ]]; then shift; fi + if [[ -z "$CTX_SESSION" ]]; then + echo "error: session name required" >&2 + echo " usage: o session " >&2 + exit 1 + fi + PREFIX="session/$CTX_SESSION" + if [[ $# -lt 1 ]]; then + echo "error: command required after 'session '" >&2 + exit 1 + fi + dispatch_cmd "$@" + ;; + agent) + shift + # Get session and agent from args or env + CTX_SESSION="${1:-${session:-}}" + if [[ -n "${1:-}" ]]; then shift; fi + CTX_AGENT="${1:-${agent:-}}" + if [[ -n "${1:-}" ]]; then shift; fi + if [[ -z "$CTX_SESSION" || -z "$CTX_AGENT" ]]; then + echo "error: session and agent names required" >&2 + echo " usage: o agent " >&2 + exit 1 + fi + PREFIX="session/$CTX_SESSION/agent/$CTX_AGENT" + if [[ $# -lt 1 ]]; then + echo "error: command required after 'agent '" >&2 + exit 1 + fi + dispatch_cmd "$@" + ;; *) - echo "unknown command: $subcmd" >&2 - echo "run 'o help' for usage" >&2 - exit 1 + # Root level - check env vars for backwards compat + if [[ -n "${agent:-}" && -n "${session:-}" ]]; then + # Has agent context from env + CTX_SESSION="$session" + CTX_AGENT="$agent" + PREFIX="session/$CTX_SESSION/agent/$CTX_AGENT" + elif [[ -n "${session:-}" ]]; then + # Has session context from env + CTX_SESSION="$session" + PREFIX="session/$CTX_SESSION" + fi + dispatch_cmd "$@" ;; esac