o: hierarchical context selectors

Refactor o CLI to use explicit context selectors:
- o <cmd>                       Root level
- o session <sname> <cmd>       Session level
- o agent <sname> <aname> <cmd> 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
This commit is contained in:
Levi Neely 2026-08-10 13:37:29 +02:00
parent 6937dda94a
commit 14a52236c8
9 changed files with 235 additions and 366 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -38,4 +38,4 @@ prompt="Explain this code${context}:
$selection
\`\`\`"
echo "$prompt" | o prompt
echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt

View File

@ -38,4 +38,4 @@ prompt="Fix this code${location}:
$selection
\`\`\`"
echo "$prompt" | o prompt
echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt

View File

@ -38,4 +38,4 @@ prompt="Refactor this code${location}:
$selection
\`\`\`"
echo "$prompt" | o prompt
echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt

View File

@ -62,4 +62,4 @@ prompt="\`\`\`${lang}
$selection
\`\`\`"
echo "$prompt" | o prompt
echo "$prompt" | o agent "$OLLIE_SESSION" "$OLLIE_AGENT" prompt

View File

@ -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"

View File

@ -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/<path>
# - Relative paths with both $session and $agent → session/$session/agent/$agent/<path>
# - No context set → path used as-is (root-relative)
# Hierarchical access:
# o <op> [args] Root level operations
# o session <sname> <op> [args] Session-level operations
# o agent <sname> <aname> <op> [args] Agent-level operations
#
# Environment variables ($session, $agent) can substitute for positional args.
#
# Subcommands:
# read <path> Read a file (-l to loop)
# write <path> [data] Write to a file (args or stdin)
# ls [path] List namespace entries
# new <session> [agent] [cwd] Create a new session or agent
# ctl <command> 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 <command> Send a control command
# prompt Interactive REPL for submitting prompts (agent-level)
# tui Launch tmux TUI (agent-level)
# generate <prompt> 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=<name> or use full path: session/<name>/agent/<agent>/$path" >&2
return 1
fi
if [[ -z "$a" ]]; then
echo "error: '$path' requires agent context" >&2
echo " export agent=<name> or use full path: session/$s/agent/<name>/$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=<name> or use full path: session/<name>/$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=<name>" >&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=<name>" >&2
return 1
fi
echo "$a"
}
# ── Subcommands ──────────────────────────────────────────────────
# ── Subcommands ─────────────────────────────────────────────────
cmd_read() {
local loop=false
@ -173,8 +47,7 @@ cmd_read() {
echo "usage: o read [-l] <path>" >&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 <prompt>" >&2
exit 1
fi
local prompt=""
if [[ $# -gt 0 ]]; then
prompt="$*"
else
prompt=$(cat)
fi
[[ -z "$prompt" ]] && { echo "usage: o generate <prompt>" >&2; exit 1; }
printf '%s' "$prompt" | ollie-9p rdwr generate
}
cmd_ctl() {
if [[ $# -lt 1 ]]; then
echo "usage: o ctl <command> [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=<name>)" >&2
if [[ -z "$PREFIX" ]]; then
echo "error: ctl requires session or agent context" >&2
echo " usage: o session <name> ctl <cmd>" >&2
echo " or: o agent <session> <agent> ctl <cmd>" >&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=<name>)" >&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 <session> <agent>" >&2
echo " or: export session=<name> agent=<name>" >&2
if [[ -z "$CTX_AGENT" ]]; then
echo "error: prompt requires agent context" >&2
echo " usage: o agent <session> <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=<name> agent=<name>" >&2
if [[ -z "$CTX_AGENT" ]]; then
echo "error: log requires agent context" >&2
echo " usage: o agent <session> <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 <session> [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 <sname> new <agent> [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 <session> [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 <session> <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 <session> [agent]" >&2
echo " or: export session=<name>" >&2
exit 1
fi
if [[ -z "$a" ]]; then
echo "error: no agent set" >&2
echo " usage: o tui <session> <agent>" >&2
echo " or: export agent=<name>" >&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 <command> [args...]
usage: o [context] <command> [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 <cmd> Root level
o session <sname> <cmd> Session level
o agent <sname> <aname> <cmd> Agent level
Commands:
read [-l] <path> Read a file (-l: loop, re-read after each return)
write <path> [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 <session> Create a new session
new <session> <agent> [cwd] Create an agent in a session
Commands (availability depends on context):
generate <prompt> One-shot generation (no session needed)
Root level:
ls List sessions, models, etc.
new <session> [cwd] Create a new session
generate <prompt> One-shot generation (no session needed)
ctl <cmd> [args] Send control command (rdwr: gets response)
Session level (o session <name> ...):
ls List session contents (agents, config)
new <agent> [cwd] Create a new agent
ctl <cmd> Session control (kill, pause, resume)
read/write <path> 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 <session> <agent> ...):
ls List agent files
ctl <cmd> 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 <path> 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] <path> Read a file (-l: loop)
write <path> [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 <name> <command>" >&2
exit 1
fi
PREFIX="session/$CTX_SESSION"
if [[ $# -lt 1 ]]; then
echo "error: command required after 'session <name>'" >&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 <session> <agent> <command>" >&2
exit 1
fi
PREFIX="session/$CTX_SESSION/agent/$CTX_AGENT"
if [[ $# -lt 1 ]]; then
echo "error: command required after 'agent <session> <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