ollie/data/scripts/o

762 lines
26 KiB
Bash
Executable File

#!/bin/bash
# o — unified CLI for the ollie 9P namespace.
#
# Context is determined by the first argument:
# o <cmd> Root level
# o <session> <cmd> Session level
# o <session>/<agent> <cmd> Agent level
# o <session>:<agent> <cmd> Agent level (colon separator)
#
# Slash is the primary separator. Agent names may contain colons.
# Use colon separator only when session names contain slashes.
#
# Subcommands:
# read <path> Read a file (-l to loop)
# write <path> [data] Write to a file (args or stdin)
# rdwr <path> [data] Write then read (request-response files)
# ls [path] List namespace entries
# new [cwd] Create session (if needed) and agent (agent-level)
# 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 export statements for context
#
# Examples:
# o ls List root (sessions, models, etc.)
# o myproj ls List session contents
# o myproj/coding new Create session+agent (cwd=$PWD)
# o myproj/coding prompt Interactive prompt
# o myproj/coding read log Read text log output
set -euo pipefail
# ── Terminal capability ──────────────────────────────────────────
USE_COLOR=true
if [[ "${TERM:-dumb}" == "dumb" ]] || [[ ! -t 1 ]]; then
USE_COLOR=false
fi
# ── 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
# ── Known commands (to distinguish from context) ─────────────────
COMMANDS="read write rdwr ls new ctl prompt log tui env generate overview bypass approve deny help"
is_command() {
local arg="$1"
for cmd in $COMMANDS; do
[[ "$arg" == "$cmd" ]] && return 0
done
[[ "$arg" == "-h" || "$arg" == "--help" ]] && return 0
return 1
}
# ── Path resolution helper ──────────────────────────────────────
# Resolves a path: if it starts with /, treat as root-level (strip /)
# Otherwise, prepend PREFIX.
resolve_path() {
local p="$1"
if [[ "$p" == /* ]]; then
# Absolute path - strip leading / and use as root-relative
echo "${p#/}"
else
# Relative path - prepend PREFIX
echo "${PREFIX}${PREFIX:+/}$p"
fi
}
# ── 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 [-l] <path>" >&2
exit 1
fi
local path
path=$(resolve_path "$1")
if $loop; then
while true; do
ollie-9p read "$path" || break
done
else
ollie-9p read "$path"
fi
}
cmd_write() {
if [[ $# -lt 1 ]]; then
echo "usage: o write <path> [data...]" >&2
exit 1
fi
local file="$1"; shift
local path
path=$(resolve_path "$file")
if [[ $# -gt 0 ]]; then
printf '%s\n' "$*" | ollie-9p write "$path"
else
ollie-9p write "$path"
fi
}
cmd_rdwr() {
if [[ $# -lt 1 ]]; then
echo "usage: o rdwr <path> [data...]" >&2
exit 1
fi
local file="$1"; shift
local path
path=$(resolve_path "$file")
if [[ $# -gt 0 ]]; then
printf '%s\n' "$*" | ollie-9p rdwr "$path"
else
ollie-9p rdwr "$path"
fi
}
cmd_ls() {
local path="${PREFIX}"
if [[ $# -ge 1 ]]; then
path=$(resolve_path "$1")
fi
ollie-9p ls "$path"
}
cmd_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
if [[ -z "$PREFIX" ]]; then
echo "usage: o <session> ctl <cmd> (or o <session>/<agent> ctl <cmd>)" >&2
exit 1
fi
# The ctl file is self-describing: an empty request returns the
# valid commands with descriptions.
echo "usage: o ctl <command> [args...]" >&2
printf '' | ollie-9p rdwr "${PREFIX}/ctl" >&2
exit 1
fi
if [[ -z "$PREFIX" ]]; then
echo "error: ctl requires session or agent context" >&2
echo " usage: o <session> ctl <cmd>" >&2
echo " or: o <session>/<agent> ctl <cmd>" >&2
exit 1
fi
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
}
cmd_prompt() {
if [[ -z "$CTX_AGENT" ]]; then
echo "error: prompt requires agent context" >&2
echo " usage: o <session>/<agent> prompt" >&2
exit 1
fi
# If stdin is not a terminal, read it all and send as a single prompt
if [[ ! -t 0 ]]; then
cat | ollie-9p write "${PREFIX}/prompt"
return
fi
# Interactive REPL mode
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, /i X (type /help for all)"
echo "! prefix: !a approve, !d deny, !b bypass, !q quit"
echo ""
local buf="" line
while true; do
local prompt_str
if [[ -z "$buf" ]]; then
if $USE_COLOR; then
prompt_str=$'\033[1m>\033[0m '
else
prompt_str='> '
fi
else
prompt_str=" "
fi
printf '%s' "$prompt_str"
if ! IFS= read -r line; then
if [[ -n "$buf" ]]; then
history -s "$buf"
printf '%s\n' "$buf" | ollie-9p write "${PREFIX}/prompt"
fi
printf "\n"
break
fi
# Drain any additional lines that arrived quickly (multi-line paste)
while IFS= read -r -t 0.1 extra_line; do
line="$line"$'\n'"$extra_line"
done
if [[ -z "$buf" && "$line" == /* ]]; then
history -s "$line"
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-${CTX_SESSION}" 2>/dev/null; exit 0 ;;
a|approve) cmd_approve ;;
d|deny) cmd_deny ;;
b|bypass) cmd_bypass ;;
esac
continue
fi
if [[ "$line" == "." ]]; then
if [[ -n "$buf" ]]; then
history -s "$buf"
printf '%s\n' "$buf" | ollie-9p write "${PREFIX}/prompt"
buf=""
fi
continue
fi
if [[ -z "$line" && -n "$buf" ]]; then
buf="$buf"$'\n'
elif [[ -z "$line" ]]; then
continue
else
if [[ -n "$buf" ]]; then
buf="$buf"$'\n'"$line"
else
buf="$line"
fi
fi
done
}
cmd_log() {
local lines=1000
while [[ $# -gt 0 && "$1" == -* ]]; do
case "$1" in
-n) shift; lines="${1:-1000}"; shift ;;
-n*) lines="${1#-n}"; shift ;;
*) break ;;
esac
done
if [[ -z "$CTX_AGENT" ]]; then
echo "error: log requires agent context" >&2
echo " usage: o <session>/<agent> log [-n N]" >&2
exit 1
fi
ollie-9p read "${PREFIX}/log" | tail -n "$lines"
}
cmd_bypass() {
if [[ -z "$CTX_SESSION" ]]; then
echo "error: bypass requires session context" >&2
echo " usage: o <session> bypass" >&2
exit 1
fi
local bypass_path="session/$CTX_SESSION/bypass"
local pending
pending=$(ollie-9p read "$bypass_path" 2>/dev/null || true)
if [[ -z "$pending" || "$pending" == "[]" || "$pending" == "null" ]]; then
echo "no pending bypass request"
return 0
fi
# Normalize to array
local arr
arr=$(echo "$pending" | jq 'if type == "array" then . else [.] end' 2>/dev/null)
# Filter by agent if agent context is set (need to look up agent ID)
if [[ -n "$CTX_AGENT" ]]; then
local agent_id
agent_id=$(ollie-9p read "session/$CTX_SESSION/agent/idx" 2>/dev/null | awk -F'\t' -v name="$CTX_AGENT" '$3 == name { print $2; exit }')
if [[ -n "$agent_id" ]]; then
arr=$(echo "$arr" | jq --arg agent "$agent_id" '[.[] | select(.env.OLLIE_UNAME == $agent)]' 2>/dev/null)
fi
fi
local count
count=$(echo "$arr" | jq 'length' 2>/dev/null)
if [[ "$count" -eq 0 ]]; then
if [[ -n "$CTX_AGENT" ]]; then
echo "no pending bypass request for agent $CTX_AGENT"
else
echo "no pending bypass request"
fi
return 0
fi
if [[ -n "$CTX_AGENT" ]]; then
echo "$count pending bypass request(s) for agent $CTX_AGENT:"
else
echo "$count pending bypass request(s):"
fi
echo
echo "$arr" | jq -r '
.[] |
"id: \(.id)\nagent: \(.env.OLLIE_UNAME // "unknown")\ncwd: \(.cwd)\ncmd: \(.cmd | split("\n")[0])\n"
' 2>/dev/null
}
cmd_approve() {
if [[ -z "$CTX_SESSION" ]]; then
echo "error: approve requires session context" >&2
echo " usage: o <session> approve [id]" >&2
exit 1
fi
local req_id="${1:-}"
local bypass_path="session/$CTX_SESSION/bypass"
local pending
pending=$(ollie-9p read "$bypass_path" 2>/dev/null || true)
if [[ -z "$pending" || "$pending" == "[]" || "$pending" == "null" ]]; then
echo "no pending bypass request"
return 0
fi
# Normalize to array
local arr
arr=$(echo "$pending" | jq 'if type == "array" then . else [.] end' 2>/dev/null)
# Filter by agent if agent context is set (need to look up agent ID)
if [[ -n "$CTX_AGENT" ]]; then
local agent_id
agent_id=$(ollie-9p read "session/$CTX_SESSION/agent/idx" 2>/dev/null | awk -F'\t' -v name="$CTX_AGENT" '$3 == name { print $2; exit }')
if [[ -n "$agent_id" ]]; then
arr=$(echo "$arr" | jq --arg agent "$agent_id" '[.[] | select(.env.OLLIE_UNAME == $agent)]' 2>/dev/null)
fi
fi
# Find the request to approve
local req
if [[ -n "$req_id" ]]; then
req=$(echo "$arr" | jq -r --arg id "$req_id" '.[] | select(.id == $id)' 2>/dev/null)
if [[ -z "$req" ]]; then
echo "error: no pending request with id $req_id" >&2
exit 1
fi
else
req=$(echo "$arr" | jq -r '.[0]' 2>/dev/null)
if [[ -z "$req" || "$req" == "null" ]]; then
if [[ -n "$CTX_AGENT" ]]; then
echo "no pending bypass request for agent $CTX_AGENT"
else
echo "no pending bypass request"
fi
return 0
fi
fi
local id cmd cwd
id=$(echo "$req" | jq -r '.id' 2>/dev/null)
cmd=$(echo "$req" | jq -r '.cmd | split("\n")[0]' 2>/dev/null)
cwd=$(echo "$req" | jq -r '.cwd' 2>/dev/null)
if [[ -z "$id" || "$id" == "null" ]]; then
echo "error: could not parse bypass request" >&2
exit 1
fi
echo "[$id] approving: $cmd"
echo " cwd: $cwd"
local result
result=$(printf '%s approve' "$id" | ollie-9p write "$bypass_path" 2>&1)
if [[ $? -eq 0 ]]; then
echo "approved"
elif [[ "$result" == *"unknown request ID"* ]]; then
echo "request already resolved (expired or handled elsewhere)"
else
echo "error: $result" >&2
exit 1
fi
}
cmd_deny() {
if [[ -z "$CTX_SESSION" ]]; then
echo "error: deny requires session context" >&2
echo " usage: o <session> deny [id]" >&2
exit 1
fi
local req_id="${1:-}"
local bypass_path="session/$CTX_SESSION/bypass"
local pending
pending=$(ollie-9p read "$bypass_path" 2>/dev/null || true)
if [[ -z "$pending" || "$pending" == "[]" || "$pending" == "null" ]]; then
echo "no pending bypass request"
return 0
fi
# Normalize to array
local arr
arr=$(echo "$pending" | jq 'if type == "array" then . else [.] end' 2>/dev/null)
# Filter by agent if agent context is set (need to look up agent ID)
if [[ -n "$CTX_AGENT" ]]; then
local agent_id
agent_id=$(ollie-9p read "session/$CTX_SESSION/agent/idx" 2>/dev/null | awk -F'\t' -v name="$CTX_AGENT" '$3 == name { print $2; exit }')
if [[ -n "$agent_id" ]]; then
arr=$(echo "$arr" | jq --arg agent "$agent_id" '[.[] | select(.env.OLLIE_UNAME == $agent)]' 2>/dev/null)
fi
fi
# Find the request to deny
local req
if [[ -n "$req_id" ]]; then
req=$(echo "$arr" | jq -r --arg id "$req_id" '.[] | select(.id == $id)' 2>/dev/null)
if [[ -z "$req" ]]; then
echo "error: no pending request with id $req_id" >&2
exit 1
fi
else
req=$(echo "$arr" | jq -r '.[0]' 2>/dev/null)
if [[ -z "$req" || "$req" == "null" ]]; then
if [[ -n "$CTX_AGENT" ]]; then
echo "no pending bypass request for agent $CTX_AGENT"
else
echo "no pending bypass request"
fi
return 0
fi
fi
local id cmd cwd
id=$(echo "$req" | jq -r '.id' 2>/dev/null)
cmd=$(echo "$req" | jq -r '.cmd | split("\n")[0]' 2>/dev/null)
cwd=$(echo "$req" | jq -r '.cwd' 2>/dev/null)
if [[ -z "$id" || "$id" == "null" ]]; then
echo "error: could not parse bypass request" >&2
exit 1
fi
echo "[$id] denying: $cmd"
echo " cwd: $cwd"
local result
result=$(printf '%s deny' "$id" | ollie-9p write "$bypass_path" 2>&1)
if [[ $? -eq 0 ]]; then
echo "denied"
elif [[ "$result" == *"unknown request ID"* ]]; then
echo "request already resolved (expired or handled elsewhere)"
else
echo "error: $result" >&2
exit 1
fi
}
cmd_new() {
if [[ -z "$CTX_AGENT" ]]; then
echo "usage: o <session>/<agent> new [cwd]" >&2
exit 1
fi
local agent_cwd="${1:-$PWD}"
# session/new is get-or-create: creating an existing session is a no-op
# success, so real failures now surface instead of being suppressed.
printf 'name=%s\ncwd=%s\n' "$CTX_SESSION" "$agent_cwd" | ollie-9p write "session/new"
# Create the agent
printf 'name=%s\ncwd=%s\n' "$CTX_AGENT" "$agent_cwd" | ollie-9p write "session/$CTX_SESSION/agent/new"
echo "created: $CTX_SESSION/$CTX_AGENT"
}
cmd_tui() {
if [[ -z "$CTX_AGENT" ]]; then
echo "error: tui requires agent context" >&2
echo " usage: o <session>/<agent> tui" >&2
exit 1
fi
local s="$CTX_SESSION"
local a="$CTX_AGENT"
# Sanitize session and agent names for tmux (replace / and : with -)
local safe_session="${s//\//-}"
safe_session="${safe_session//:/-}"
local safe_agent="${a//\//-}"
safe_agent="${safe_agent//:/-}"
local tmux_session="o-${safe_session}-${safe_agent}"
# Check for subcommands
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
tmux kill-session -t "${tmux_session}" 2>/dev/null || true
# Layout: log (top 80%), prompt (bottom 20%)
# Use slash separator - session names don't contain slashes, agent names might contain colons
local ctx="$s/$a"
tmux new-session -d -s "$tmux_session" -c "$HOME"
tmux set-option -t "$tmux_session" mouse on
sleep 0.3 # Wait for shell to initialize
tmux send-keys -t "$tmux_session" "o '$ctx' read log; o '$ctx' read chat" Enter
tmux split-window -t "$tmux_session" -v -l 20%
sleep 0.2
tmux send-keys -t "$tmux_session" "o '$ctx' prompt" Enter
# Get initial state directly from the state file, then style it the same
# way the event loop below does, so the status bar is correct immediately.
local initial_state initial_status initial_bg
initial_state=$(ollie-9p read "session/$s/agent/$a/state" 2>/dev/null || echo "unknown")
case "$initial_state" in
idle) initial_bg="colour70" ;;
calling*) initial_bg="colour214" ;;
thinking) initial_bg="colour33" ;;
paused) initial_bg="colour245" ;;
*) initial_bg="default" ;;
esac
initial_status=$(ollie-9p read "${PREFIX}/status" 2>/dev/null | head -n1)
tmux set-option -t "$tmux_session" status-right-style "fg=default,bg=$initial_bg"
tmux set-option -t "$tmux_session" status-right "${initial_status:-$initial_state} | %H:%M"
tmux set-option -t "$tmux_session" status-right-length 64
# Get session and agent IDs for event subscription
local sess_id agent_id
sess_id=$(ollie-9p read "session/idx" 2>/dev/null | awk -F'\t' -v name="$s" '$2 == name { print $1; exit }')
agent_id=$(ollie-9p read "session/$s/agent/idx" 2>/dev/null | awk -F'\t' -v name="$a" '$3 == name { print $2; exit }')
if [[ -n "$sess_id" && -n "$agent_id" ]]; then
# Subscribe to state events for this agent
echo "session.${sess_id}.agent.${agent_id}.state" | ollie-9p rdwrs event 2>/dev/null | while read -r topic state; do
bg=""
case "$state" in
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
status=$(ollie-9p read "${PREFIX}/status" 2>/dev/null | head -n1)
tmux set-option -t "$tmux_session" status-right "${status:-$state} | %H:%M" 2>/dev/null
done &
local bg_pid=$!
trap "kill $bg_pid 2>/dev/null" EXIT
fi
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 OLLIE_SESSION=$CTX_SESSION"
[[ -n "$CTX_AGENT" ]] && echo "export OLLIE_AGENT=$CTX_AGENT"
}
# cmd_overview shows all running sessions and their agents with a human-readable
# status, so you can see what's live without knowing any names in advance.
cmd_overview() {
local idx
idx=$(ollie-9p read "session/idx" 2>/dev/null || true)
if [[ -z "$idx" ]]; then
echo "no active sessions"
echo "start one: o <session>/<agent> new [cwd]"
return
fi
# session/idx: id \t name \t paused \t connected \t remote \t cwd
while IFS=$'\t' read -r _id sname paused _conn remote cwd; do
[[ -z "$sname" ]] && continue
local tag=""
[[ "$paused" == "true" ]] && tag=" (paused)"
[[ -n "$remote" ]] && tag="$tag [remote: $remote]"
printf '%s%s\t%s\n' "$sname" "$tag" "$cwd"
# agent idx: sess-id \t agent-id \t agent-name \t parent-id \t depth \t state
local aidx
aidx=$(ollie-9p read "session/$sname/agent/idx" 2>/dev/null || true)
while IFS=$'\t' read -r _sid _aid aname _pid _depth _state; do
[[ -z "$aname" ]] && continue
local status
status=$(ollie-9p read "session/$sname/agent/$aname/status" 2>/dev/null | head -n1)
printf ' %s/%s\t%s\n' "$sname" "$aname" "${status:-?}"
done <<< "$aidx"
done <<< "$idx"
}
cmd_help() {
cat <<'EOF'
usage: o [context] <command> [args...]
Context is determined by the first argument:
o <cmd> Root level
o <session> <cmd> Session level
o <session>/<agent> <cmd> Agent level
o <session>:<agent> <cmd> Agent level (colon separator)
Slash is the primary separator. Agent names may contain colons.
Use colon separator only when session names contain slashes.
Paths starting with / are root-relative (ignore context):
o myproj ls /bypass List root bypass/ even in session context
Commands (availability depends on context):
Root level:
overview Show running sessions/agents and their status
ls List sessions, models, etc.
generate <prompt> One-shot generation (no session needed)
Session level (o <session> ...):
ls List session contents (agents, config)
ctl <cmd> Session control (kill, pause, resume)
bypass Show pending bypass request details
approve [id] Approve pending bypass request
deny [id] Deny pending bypass request
read/write <path> Access session files
Agent level (o <session>/<agent> or <session>:<agent> ...):
new [cwd] Create session (if needed) and 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
bypass Show pending bypass request (session-level)
approve [id] Approve pending bypass request (session-level)
deny [id] Deny pending bypass request (session-level)
read/write <path> Access agent files (log, cfg, etc.)
Common:
read [-l] <path> Read a file (-l: loop)
write <path> [data] Write to a file
rdwr <path> [data] Write then read (request-response)
env Print export statements for context
help Show this help
Examples:
o ls # list root (sessions, models)
o myproj/coding new # create session+agent (cwd=$PWD)
o myproj:coding new # same, using colon separator
o "my proj:coding" new # session name with space
o myproj/coding new ~/src # create with specific cwd
o myproj ls # list session contents
o myproj/coding prompt # interactive prompt
o myproj/coding read log # read text log
o myproj bypass # show pending bypass details
o myproj approve # approve pending bypass
o myproj approve 42 # approve specific request by id
o generate "explain monads" # one-shot, no session
EOF
}
# ── Dispatch ────────────────────────────────────────────────────
dispatch_cmd() {
local cmd="$1"; shift
case "$cmd" in
read) cmd_read "$@" ;;
write) cmd_write "$@" ;;
rdwr) cmd_rdwr "$@" ;;
ls) cmd_ls "$@" ;;
generate) cmd_generate "$@" ;;
ctl) cmd_ctl "$@" ;;
prompt) cmd_prompt "$@" ;;
log) cmd_log "$@" ;;
new) cmd_new "$@" ;;
tui) cmd_tui "$@" ;;
env) cmd_env "$@" ;;
overview) cmd_overview "$@" ;;
bypass) cmd_bypass "$@" ;;
approve) cmd_approve "$@" ;;
deny) cmd_deny "$@" ;;
help|-h|--help) cmd_help ;;
*)
echo "unknown command: $cmd" >&2
echo "run 'o help' for usage" >&2
exit 1
;;
esac
}
# Parse context from first argument
# Supports:
# session/agent (slash separator - primary)
# session:agent (colon separator - for session names with slashes)
# "session name/agent" (quoted with spaces)
#
# Slash is the primary separator. Use colon only when session names contain slashes.
# Agent names may contain colons (e.g., "src:ollie").
parse_context() {
local arg="$1"
# Slash is the primary separator
if [[ "$arg" == */* ]]; then
CTX_SESSION="${arg%%/*}"
CTX_AGENT="${arg#*/}"
elif [[ "$arg" == *:* ]]; then
# Colon separator (for session names containing slashes)
CTX_SESSION="${arg%%:*}"
CTX_AGENT="${arg#*:}"
else
# session only
CTX_SESSION="$arg"
CTX_AGENT=""
fi
# Build prefix
if [[ -n "$CTX_AGENT" ]]; then
PREFIX="session/$CTX_SESSION/agent/$CTX_AGENT"
else
PREFIX="session/$CTX_SESSION"
fi
}
# Note: We do NOT re-split arguments here. The previous `set -- $*` was
# removed because it breaks quoted arguments with spaces. If callers pass
# a single quoted string, they should use eval or proper quoting.
# For acme integration, the calling script should handle argument parsing.
# Main dispatch
if [[ $# -lt 1 ]]; then
cmd_overview
echo ""
echo "run 'o help' for usage"
exit 0
fi
# Check if first arg is a command (root level) or context
if is_command "$1"; then
# Root level - no context
dispatch_cmd "$@"
else
# First arg is context (session or session/agent)
parse_context "$1"
shift
if [[ $# -lt 1 ]]; then
# No command after context - default to ls
cmd_ls
else
dispatch_cmd "$@"
fi
fi