#!/usr/bin/env bash
#clear
#export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m'
#set -x

#echo "starting: $0 <LOG_LEVEL=$1>"

### new.method

# ============================================================================
# claudeCode - Claude Code wrapper for oosh
# Makes Claude Code CLI flags easy to use with intuitive method names
# ============================================================================

# Path to claude binary - no need to have it in PATH
CLAUDE_CMD="$HOME/.local/bin/claude"
export FORCE_COLOR=2
unset  COLORTERM
unset  CLAUDECODE

# ── Shared completion helpers ──────────────────────────────────────────────
private.claudeCode.complete.panes() {
  tmux list-panes -a -F "#{session_name}:#{window_index}.#{pane_index}" 2>/dev/null
}
private.claudeCode.complete.sessionIds() {
  local claudeProjectsDir="$HOME/.claude/projects"
  for projectDir in "$claudeProjectsDir"/*; do
    [ ! -d "$projectDir" ] && continue
    local indexFile="$projectDir/sessions-index.json"
    if [ -f "$indexFile" ] && command -v jq &>/dev/null; then
      jq -r '.entries[].sessionId' "$indexFile" 2>/dev/null
    else
      ls "$projectDir"/*.jsonl 2>/dev/null | xargs -I{} basename {} .jsonl
    fi
  done
}
private.claudeCode.complete.roleNames() {
  local reg="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
  [ -f "$reg" ] && cut -d'|' -f2 "$reg" 2>/dev/null | sort -u
}

claudeCode.sessions() # # list all available sessions (interactive picker)
{
  "$CLAUDE_CMD" --resume
}

claudeCode.list() # <?--json> # list all Claude Code sessions on this host in tree format
{
  local CLAUDE_PROJECTS_DIR="$HOME/.claude/projects"
  local format="tree"

  [ "$1" = "--json" ] && format="json"

  if [ ! -d "$CLAUDE_PROJECTS_DIR" ]; then
    echo "No Claude projects directory found: $CLAUDE_PROJECTS_DIR"
    return 1
  fi

  if [ "$format" = "json" ]; then
    claudeCode.list.json
    return $?
  fi

  echo ""
  echo "┌─────────────────────────────────────────────────────────────────────────────┐"
  echo "│                        CLAUDE CODE SESSIONS                                 │"
  echo "├─────────────────────────────────────────────────────────────────────────────┤"
  echo "│ ID                                    Branch      Msgs  Modified    Name    │"
  echo "└─────────────────────────────────────────────────────────────────────────────┘"
  echo ""

  local totalSessions=0
  local totalProjects=0

  for projectDir in "$CLAUDE_PROJECTS_DIR"/*; do
    [ ! -d "$projectDir" ] && continue

    local projectName=$(basename "$projectDir")
    local indexFile="$projectDir/sessions-index.json"
    local decodedPath=$(echo "$projectName" | sed 's/^-/\//' | sed 's/-/\//g')

    ((totalProjects++))

    echo "📁 $decodedPath"

    if [ -f "$indexFile" ] && command -v jq &>/dev/null; then
      local entries=$(jq -r '.entries | length' "$indexFile" 2>/dev/null)

      if [ "$entries" -gt 0 ] 2>/dev/null; then
        local idx=0
        jq -r '.entries[] | "\(.sessionId)|\(.firstPrompt // "untitled")|\(.messageCount // 0)|\(.modified // "unknown")|\(.gitBranch // "-")"' "$indexFile" 2>/dev/null | \
        while IFS='|' read -r sid prompt msgs modified branch; do
          ((idx++))
          ((totalSessions++))

          local shortPrompt=$(echo "$prompt" | cut -c1-45)
          [ ${#prompt} -gt 45 ] && shortPrompt="${shortPrompt}..."
          local modDate=$(echo "$modified" | cut -c1-10)

          local prefix="├──"
          [ "$idx" -eq "$entries" ] && prefix="└──"

          printf "%s %-36s  %-10s  %3s  %s  %s\n" "$prefix" "$sid" "$branch" "$msgs" "$modDate" "$shortPrompt"
        done
      else
        echo "└── (no sessions)"
      fi
    else
      local jsonlCount=$(ls "$projectDir"/*.jsonl 2>/dev/null | wc -l)
      if [ "$jsonlCount" -gt 0 ]; then
        ls "$projectDir"/*.jsonl 2>/dev/null | while read f; do
          local sid=$(basename "$f" .jsonl)
          echo "├── ${sid:0:8}..."
          ((totalSessions++))
        done
      else
        echo "└── (no sessions)"
      fi
    fi
    echo ""
  done

  echo "─────────────────────────────────────────────────────────────────────────────"
  echo "Total: $totalProjects projects"
}

claudeCode.list.named() # # list only sessions that have a custom name (set via /rename)
{
  local CLAUDE_PROJECTS_DIR="$HOME/.claude/projects"
  local total=0

  echo ""
  echo "NAMED CLAUDE CODE SESSIONS"
  echo "─────────────────────────────────────────────────────────────────────────────"
  echo "ID                                    Name                       Msgs  Modified"
  echo "─────────────────────────────────────────────────────────────────────────────"
  echo ""

  for projectDir in "$CLAUDE_PROJECTS_DIR"/*; do
    [ ! -d "$projectDir" ] && continue

    local projectName=$(basename "$projectDir")
    local indexFile="$projectDir/sessions-index.json"
    local decodedPath=$(echo "$projectName" | sed 's/^-/\//' | sed 's/-/\//g')

    if [ -f "$indexFile" ] && command -v jq &>/dev/null; then
      local namedEntries
      namedEntries=$(jq -r '.entries[] | select(.customTitle != null) | select(.customTitle != "") | [.sessionId, .customTitle, (.messageCount // 0 | tostring), (.modified // "?" | .[:10])] | join("|")' "$indexFile" 2>/dev/null)

      [ -z "$namedEntries" ] && continue

      console.log "$decodedPath"
      echo "$namedEntries" | while IFS='|' read -r sid title msgs modDate; do
        ((total++))
        local shortTitle=$(echo "$title" | cut -c1-25)
        [ ${#title} -gt 25 ] && shortTitle="${shortTitle}..."
        printf "  %-36s  %-28s  %3s  %s\n" "$sid" "$shortTitle" "$msgs" "$modDate"
      done
      echo ""
    fi
  done

  echo "─────────────────────────────────────────────────────────────────────────────"
}

claudeCode.list.json() # # output session list as JSON
{
  local CLAUDE_PROJECTS_DIR="$HOME/.claude/projects"
  echo "["
  local firstProject=true

  for projectDir in "$CLAUDE_PROJECTS_DIR"/*; do
    [ ! -d "$projectDir" ] && continue

    local projectName=$(basename "$projectDir")
    local decodedPath=$(echo "$projectName" | sed 's/^-/\//' | sed 's/-/\//g')
    local indexFile="$projectDir/sessions-index.json"

    if [ -f "$indexFile" ] && command -v jq &>/dev/null; then
      jq -r --arg dir "$decodedPath" '.entries[] | {sessionId, firstPrompt, messageCount, modified, gitBranch, directory: $dir}' "$indexFile" 2>/dev/null | \
      while read -r line; do
        [ "$firstProject" = "false" ] && echo ","
        firstProject=false
        echo "$line"
      done
    fi
  done
  echo "]"
}

claudeCode.join() # <session> # resume a specific session by name or UUID
{
  local session="$1"
  if [ -n "$session" ]; then
    shift
    # If not a UUID, try role→pane→UUID or pane→UUID lookup
    if ! [[ "$session" =~ ^[0-9a-f]{8}-[0-9a-f]{4} ]]; then
      local reg="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
      local ses="${HIVEMIND_SESSIONS:-${CONFIG_PATH:-$HOME/config}/hivemind.sessions.env}"
      local foundUuid=""
      # Try as pane target first (new schema: pane|UUID)
      [ -f "$ses" ] && foundUuid=$(grep "^${session}|" "$ses" 2>/dev/null | head -1 | cut -d'|' -f2)
      # Try as role name: resolve role→pane→UUID
      if [ -z "$foundUuid" ] && [ -f "$reg" ]; then
        local pane
        pane=$(grep "|${session}$" "$reg" 2>/dev/null | head -1 | cut -d'|' -f1)
        [ -n "$pane" ] && [ -f "$ses" ] && foundUuid=$(grep "^${pane}|" "$ses" 2>/dev/null | head -1 | cut -d'|' -f2)
      fi
      [ -n "$foundUuid" ] && session="$foundUuid"
    fi
    # Update sessions file with the UUID being resumed (keyed by pane)
    local paneTarget
    paneTarget=$(otmux pane.get.target 2>/dev/null)
    if [ -n "$paneTarget" ]; then
      local ses="${HIVEMIND_SESSIONS:-${CONFIG_PATH:-$HOME/config}/hivemind.sessions.env}"
      if [ -f "$ses" ]; then
        grep -v "^${paneTarget}|" "$ses" > "${ses}.tmp" 2>/dev/null
        mv "${ses}.tmp" "$ses"
      fi
      echo "${paneTarget}|${session}" >> "$ses"
    fi
    "$CLAUDE_CMD" --resume "$session" "$@"
  else
    # No session specified, show picker
    "$CLAUDE_CMD" --resume
  fi
}
claudeCode.join.completion.session() {
  private.claudeCode.complete.sessionIds
}

claudeCode.join.byID() # <sessionId> # resume session directly by UUID
{
  local sessionId="$1"
  [ -z "$sessionId" ] && { error.log "usage: claudeCode join.byID <sessionId>"; return 1; }
  shift
  "$CLAUDE_CMD" --resume "$sessionId" "$@"
}

claudeCode.join.byName() # <name> # resume session by role name (role→pane→UUID lookup)
{
  local name="$1"
  [ -z "$name" ] && { error.log "usage: claudeCode join.byName <name>"; return 1; }
  shift
  local reg="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
  local ses="${HIVEMIND_SESSIONS:-${CONFIG_PATH:-$HOME/config}/hivemind.sessions.env}"
  # Resolve role → pane → UUID
  local pane
  pane=$(grep "|${name}$" "$reg" 2>/dev/null | head -1 | cut -d'|' -f1)
  if [ -z "$pane" ]; then
    error.log "no pane found for role '$name' in registry"
    return 1
  fi
  local foundUuid
  foundUuid=$(grep "^${pane}|" "$ses" 2>/dev/null | head -1 | cut -d'|' -f2)
  if [ -z "$foundUuid" ]; then
    error.log "no session UUID found for pane '$pane' (role '$name')"
    return 1
  fi
  "$CLAUDE_CMD" --resume "$foundUuid" "$@"
}
claudeCode.join.byName.completion.name() {
  private.claudeCode.complete.roleNames
}

claudeCode.join.byPane() # <pane> # resume session by resolving pane → UUID
{
  local pane="$1"
  [ -z "$pane" ] && { error.log "usage: claudeCode join.byPane <pane>"; return 1; }
  shift
  local sid
  sid=$(claudeCode.session.id "$pane" 2>/dev/null)
  if [ -z "$sid" ]; then
    info.log "session.id failed, trying session.probe (slow ~3s)..."
    sid=$(claudeCode.session.probe "$pane" 2>/dev/null)
  fi
  if [ -z "$sid" ]; then
    error.log "could not resolve session UUID for pane '$pane'"
    return 1
  fi
  "$CLAUDE_CMD" --resume "$sid" "$@"
}

claudeCode.fork() # <sessionId> # fork session with new ID, preserving conversation history
{
  local sessionId="$1"
  [ -z "$sessionId" ] && { error.log "usage: claudeCode fork <sessionId>"; return 1; }
  shift
  "$CLAUDE_CMD" --resume "$sessionId" --fork-session "$@"
}

claudeCode.continue() # # continue the most recent session without picker
{
  "$CLAUDE_CMD" --continue "$@"
}

claudeCode.c() # # shorthand for continue
{
  claudeCode.continue "$@"
}

claudeCode.new() # <?prompt> # start a fresh conversation with optional initial prompt
{
  if [ -n "$1" ]; then
    "$CLAUDE_CMD" --print "$@"
  else
    "$CLAUDE_CMD"
  fi
}

claudeCode.print() # <prompt> # non-interactive: print response and exit
{
  "$CLAUDE_CMD" --print "$@"
}

claudeCode.p() # <prompt> # shorthand for print
{
  claudeCode.print "$@"
}

claudeCode.dangerously() # <prompt> # skip all permission prompts (use with caution!)
{
  "$CLAUDE_CMD" --dangerously-skip-permissions "$@"
}

claudeCode.yolo() # <prompt> # alias for dangerously - skip permissions
{
  claudeCode.dangerously "$@"
}

claudeCode.verbose() # <?prompt> # run with verbose output
{
  "$CLAUDE_CMD" --verbose "$@"
}

claudeCode.model() # <model> <?prompt> # use specific model (sonnet, opus, haiku)
{
  local model="$1"
  if [ -n "$model" ]; then
    shift
    "$CLAUDE_CMD" --model "$model" "$@"
  else
    error.log "usage: cc model <sonnet|opus|haiku> [prompt]"
    return 1
  fi
}
claudeCode.model.completion.model() {
  echo "sonnet"
  echo "opus"
  echo "haiku"
}

claudeCode.model.list() # # list available model aliases
{
  echo "opus"
  echo "sonnet"
  echo "haiku"
}
claudeCode.model.list.completion() { :; }

claudeCode.model.set() # <pane> <model> # switch model in running session via /model
{
  local pane="$1"
  local model="$2"

  [ -z "$pane" ] || [ -z "$model" ] && { error.log "Usage: claudeCode model.set <pane> <model>"; return 1; }

  # Validate model alias
  case "$model" in
    opus|sonnet|haiku) ;;
    *) error.log "Invalid model: $model (use opus, sonnet, haiku)"; return 1 ;;
  esac

  # Send /model command to the pane
  "$OOSH_DIR/otmux" send.enter "$pane" "/model $model"
  info.log "Sent /model $model to $pane"
}
claudeCode.model.set.completion.model() {
  claudeCode.model.list
}

claudeCode.model.get() # <pane> # get current model from running session
{
  local pane="$1"
  [ -z "$pane" ] && { error.log "Usage: claudeCode model.get <pane>"; return 1; }

  # Capture TUI status bar — Claude Code shows model in status line
  local content
  content=$("$OOSH_DIR/otmux" pane.capture "$pane" 10)

  # Parse model from status bar (format: "Model: opus" or similar)
  local model
  model=$(echo "$content" | grep -oiE '(opus|sonnet|haiku)' | head -1 | tr '[:upper:]' '[:lower:]')

  if [ -n "$model" ]; then
    RESULT="$model"
    echo "$model"
    return 0
  fi

  # Fallback: unknown (TUI may not show model)
  RESULT="unknown"
  echo "unknown"
  return 1
}

claudeCode.opus() # <?prompt> # use opus model
{
  "$CLAUDE_CMD" --model opus "$@"
}

claudeCode.sonnet() # <?prompt> # use sonnet model
{
  "$CLAUDE_CMD" --model sonnet "$@"
}

claudeCode.haiku() # <?prompt> # use haiku model
{
  "$CLAUDE_CMD" --model haiku "$@"
}

claudeCode.chat() # <?prompt> # interactive chat mode (default)
{
  "$CLAUDE_CMD" "$@"
}

claudeCode.help() # # show claude help
{
  "$CLAUDE_CMD" --help
}

claudeCode.version() # # show claude version
{
  "$CLAUDE_CMD" --version
}

claudeCode.v() # # shorthand for version
{
  claudeCode.version
}

claudeCode.config() # # open claude settings/config
{
  "$CLAUDE_CMD" config
}

claudeCode.doctor() # # run claude doctor to check setup
{
  "$CLAUDE_CMD" doctor
}

claudeCode.mcp() # # manage MCP servers
{
  "$CLAUDE_CMD" mcp "$@"
}

claudeCode.tools.allowed() # <tools> # restrict to specific tools (comma-separated)
{
  local tools="$1"
  if [ -n "$tools" ]; then
    shift
    "$CLAUDE_CMD" --allowedTools "$tools" "$@"
  else
    error.log "usage: cc allowedTools <tool1,tool2,...> [prompt]"
    return 1
  fi
}

claudeCode.tools.disallowed() # <tools> # block specific tools (comma-separated)
{
  local tools="$1"
  if [ -n "$tools" ]; then
    shift
    "$CLAUDE_CMD" --disallowedTools "$tools" "$@"
  else
    error.log "usage: cc disallowedTools <tool1,tool2,...> [prompt]"
    return 1
  fi
}

claudeCode.turns.max() # <turns> <?prompt> # limit conversation turns
{
  local turns="$1"
  if [ -n "$turns" ]; then
    shift
    "$CLAUDE_CMD" --max-turns "$turns" "$@"
  else
    error.log "usage: cc maxTurns <number> [prompt]"
    return 1
  fi
}

claudeCode.system.prompt() # <prompt> <?message> # set custom system prompt
{
  local sysprompt="$1"
  if [ -n "$sysprompt" ]; then
    shift
    "$CLAUDE_CMD" --system-prompt "$sysprompt" "$@"
  else
    error.log "usage: cc systemPrompt <prompt> [message]"
    return 1
  fi
}

claudeCode.system.prompt.append() # <prompt> <?message> # append to system prompt
{
  local sysprompt="$1"
  if [ -n "$sysprompt" ]; then
    shift
    "$CLAUDE_CMD" --append-system-prompt "$sysprompt" "$@"
  else
    error.log "usage: cc appendSystemPrompt <prompt> [message]"
    return 1
  fi
}

claudeCode.output() # <format> <?prompt> # set output format (text, json, stream-json)
{
  local format="$1"
  if [ -n "$format" ]; then
    shift
    "$CLAUDE_CMD" --output-format "$format" "$@"
  else
    error.log "usage: cc output <text|json|stream-json> [prompt]"
    return 1
  fi
}
claudeCode.output.completion.format() {
  echo "text"
  echo "json"
  echo "stream-json"
}

claudeCode.json() # <?prompt> # output as JSON
{
  "$CLAUDE_CMD" --output-format json "$@"
}

claudeCode.pipe() # # read from stdin (for piping)
{
  "$CLAUDE_CMD" -p "$@"
}

claudeCode.init() # <?path> # initialize claude in a directory
{
  if [ -n "$1" ]; then
    "$CLAUDE_CMD" init "$1"
  else
    "$CLAUDE_CMD" init
  fi
}

claudeCode.update() # # update claude code
{
  "$CLAUDE_CMD" update
}

claudeCode.login() # # login to claude
{
  "$CLAUDE_CMD" login
}

claudeCode.logout() # # logout from claude
{
  "$CLAUDE_CMD" logout
}

claudeCode.install() # # install claude code from web for linux
{
  local INSTALL_DIR="${HOME}/.local/bin"
  local CONFIG_FILE="${CONFIG:-$HOME/config/user.env}"

  # Check if already installed
  if command -v claude &> /dev/null; then
    info.log "Claude Code is already installed: $(which claude)"
    "$CLAUDE_CMD" --version
    echo ""
    read -p "Reinstall/update anyway? [y/N] " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
      info.log "Installation cancelled"
      return 0
    fi
  fi

  info.log "Installing Claude Code for Linux..."

  # Ensure curl is installed (silent if already present)
  oo cmd curl

  # Download and run the official installer
  info.log "Downloading and running official installer..."
  curl -fsSL https://claude.ai/install.sh | bash

  if [ $? -ne 0 ]; then
    error.log "Installation failed"
    return 1
  fi

  # Check if ~/.local/bin is in PATH via config
  if [ -f "$CONFIG_FILE" ]; then
    if ! grep -q '\.local/bin' "$CONFIG_FILE"; then
      info.log "Adding ~/.local/bin to PATH in config..."
      # Read current PATH from config
      local currentPath=$(grep '^export PATH=' "$CONFIG_FILE" | head -1)
      if [ -n "$currentPath" ]; then
        # Check if .local/bin is already in the PATH value
        if ! echo "$currentPath" | grep -q '\.local/bin'; then
          # Append to existing PATH
          sed -i 's|^export PATH="\(.*\)"$|export PATH="\1:'"$HOME"'/.local/bin"|' "$CONFIG_FILE"
          info.log "Updated PATH in $CONFIG_FILE"
        fi
      else
        # Add new PATH export
        echo "export PATH=\"\$PATH:$HOME/.local/bin\"" >> "$CONFIG_FILE"
        info.log "Added PATH to $CONFIG_FILE"
      fi
    else
      info.log "~/.local/bin already in config PATH"
    fi
  else
    warn.log "Config file not found at $CONFIG_FILE"
    warn.log "Add ~/.local/bin to your PATH manually if needed"
  fi

  # Add to current session PATH if not present
  if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
    export PATH="$PATH:$INSTALL_DIR"
    info.log "Added $INSTALL_DIR to current session PATH"
  fi

  # Verify installation
  if command -v claude &> /dev/null; then
    success.log "Claude Code installed successfully!"
    "$CLAUDE_CMD" --version
    echo ""
    info.log "Run 'claudeCode login' to authenticate"
  else
    error.log "Installation completed but 'claude' command not found"
    error.log "You may need to restart your shell or add ~/.local/bin to PATH"
    return 1
  fi
}

claudeCode.uninstall() # # uninstall claude code
{
  local INSTALL_DIR="${HOME}/.local"
  local CLAUDE_DIR="${INSTALL_DIR}/share/claude"
  local CLAUDE_BIN="${INSTALL_DIR}/bin/claude"

  if [ ! -f "$CLAUDE_BIN" ] && [ ! -d "$CLAUDE_DIR" ]; then
    warn.log "Claude Code does not appear to be installed"
    return 0
  fi

  echo "This will remove:"
  [ -f "$CLAUDE_BIN" ] && echo "  - $CLAUDE_BIN"
  [ -d "$CLAUDE_DIR" ] && echo "  - $CLAUDE_DIR"
  echo ""
  read -p "Are you sure you want to uninstall Claude Code? [y/N] " -n 1 -r
  echo

  if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    info.log "Uninstall cancelled"
    return 0
  fi

  info.log "Removing Claude Code..."

  # Remove symlink/binary
  if [ -f "$CLAUDE_BIN" ] || [ -L "$CLAUDE_BIN" ]; then
    rm -f "$CLAUDE_BIN"
    info.log "Removed $CLAUDE_BIN"
  fi

  # Remove installation directory
  if [ -d "$CLAUDE_DIR" ]; then
    rm -rf "$CLAUDE_DIR"
    info.log "Removed $CLAUDE_DIR"
  fi

  success.log "Claude Code uninstalled successfully"
  info.log "Note: ~/.claude config directory was preserved"
}

# ─────────────────────────────────────────────────────────────────────────────
# AGENT SUPPORT
# ─────────────────────────────────────────────────────────────────────────────

claudeCode.process.find() # <pane> # find Claude Code PID running in a tmux pane
{
  local target="$1"
  [ -z "$target" ] && return 1

  local tty
  tty=$(otmux pane.get "$target" "#{pane_tty}" 2>/dev/null)
  [ -z "$tty" ] && return 1
  local ttyShort="${tty#/dev/}"

  # Search full command line (args) for 'claude' on this TTY.
  # Using args instead of comm avoids truncation when Claude is installed
  # at a long path like ~/.local/bin/claude.
  local pid
  pid=$(ps -eo pid,tty,args 2>/dev/null | awk -v t="$ttyShort" '$2 == t' | grep -i 'claude' | awk '{print $1}' | head -1)
  [ -n "$pid" ] && echo "$pid" && return 0
  return 1
}

claudeCode.process.running() # <pane> # check if Claude Code is running in a tmux pane
{
  claudeCode.process.find "$1" > /dev/null 2>&1
}

claudeCode.session.probe() # <pane> # get current session UUID from /status (ground truth, slow ~3s)
{
  local target="$1"
  [ -z "$target" ] && return 1

  # Send /status, wait for TUI to render, capture, dismiss
  otmux send "$target" "/status" Enter
  sleep 3
  local capture
  capture=$(otmux pane.capture "$target" 40 2>/dev/null)
  otmux send "$target" Escape

  # Parse "Session ID: <uuid>"
  local sid
  sid=$(echo "$capture" | grep -oE 'Session ID: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
  [ -n "$sid" ] && echo "$sid" && return 0

  return 1
}

claudeCode.session.id() # <pane> # get Claude Code session UUID for a tmux pane
{
  local target="$1"
  [ -z "$target" ] && return 1

  # Method 1 (FIRST): extract session UUID from process args — ground truth
  # Matches: --resume <UUID>, claudeCode join <UUID>
  # SKIP for forked sessions: --fork-session makes --resume UUID stale (it's the parent UUID)
  local claudePid
  claudePid=$(claudeCode.process.find "$target")
  if [ -n "$claudePid" ]; then
    local proc_args
    proc_args=$(ps -p "$claudePid" -o args= 2>/dev/null)
    if ! echo "$proc_args" | grep -q '\-\-fork-session'; then
      local sid
      sid=$(echo "$proc_args" | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
      [ -n "$sid" ] && echo "$sid" && return 0
    fi
    # Forked session: fall through to registry/sessions.env lookup
  fi

  # Method 0 (FALLBACK): look up pane → session UUID from sessions.env
  # Only reached when process has no --resume UUID (e.g. fresh claude without resume)
  local ses="${HIVEMIND_SESSIONS:-${CONFIG_PATH:-$HOME/config}/hivemind.sessions.env}"
  if [ -f "$ses" ]; then
    local sid
    sid=$(grep "^${target}|" "$ses" 2>/dev/null | tail -1 | cut -d'|' -f2)
    [ -n "$sid" ] && echo "$sid" && return 0
  fi

  return 1
}

claudeCode.session.name() # <sessionId> # get session name by UUID (customTitle from /rename, or firstPrompt)
{
  local sid="$1"
  [ -z "$sid" ] && return 1

  local CLAUDE_PROJECTS_DIR="$HOME/.claude/projects"
  local name=""

  # Method 1: look up in sessions-index.json (customTitle preferred, firstPrompt fallback)
  for indexFile in "$CLAUDE_PROJECTS_DIR"/*/sessions-index.json; do
    [ -f "$indexFile" ] || continue
    name=$(jq -r --arg sid "$sid" '
      .entries[] | select(.sessionId == $sid) |
      if .customTitle and .customTitle != "" then .customTitle
      elif .firstPrompt and .firstPrompt != "" then .firstPrompt[0:40]
      else empty end
    ' "$indexFile" 2>/dev/null)
    [ -n "$name" ] && break
  done

  if [ -n "$name" ]; then
    echo "$name"
    return 0
  fi

  # Method 2: check JSONL for custom-title entry (from /rename, more current than index)
  for projectDir in "$CLAUDE_PROJECTS_DIR"/*/; do
    local jsonlFile="${projectDir}${sid}.jsonl"
    [ -f "$jsonlFile" ] || continue
    name=$(grep '"custom-title"' "$jsonlFile" 2>/dev/null | tail -1 \
      | jq -r '.customTitle // empty' 2>/dev/null \
      | head -1)
    name="${name%%$'\n'*}"
    [ -n "$name" ] && break
  done

  if [ -n "$name" ]; then
    echo "$name"
    return 0
  fi

  # Method 3: read firstPrompt directly from JSONL file (for sessions not yet in index)
  for projectDir in "$CLAUDE_PROJECTS_DIR"/*/; do
    local jsonlFile="${projectDir}${sid}.jsonl"
    [ -f "$jsonlFile" ] || continue
    name=$(head -20 "$jsonlFile" \
      | jq -r 'select(.type == "user") | .message.content // empty' 2>/dev/null \
      | sed 's/<[^>]*>//g; s/^[[:space:]]*//; /^$/d' \
      | grep -vi '^caveat:' \
      | grep -v '^/clear' \
      | grep -vi '^clear$' \
      | head -1)
    [ -n "$name" ] && name="${name:0:40}" && break
  done

  if [ -n "$name" ]; then
    echo "$name"
    return 0
  fi

  return 1
}

claudeCode.agent.start() # <?workdir:.> <?model:sonnet> # start Claude Code with optional model
{
  local workdir="$(pwd)"
  local model=""

  # Parse arguments
  while [ $# -gt 0 ]; do
    case "$1" in
      --model) model="$2"; shift 2 ;;
      *) workdir="$1"; shift ;;
    esac
  done

  local cmd="$CLAUDE_CMD"
  [ -n "$model" ] && cmd="$cmd --model $model"

  if [ -n "$TMUX" ]; then
    local currentPane
    currentPane=$(otmux pane.get)
    "$OOSH_DIR/otmux" send "$currentPane" "cd '$workdir' && $cmd" Enter
    info.log "Started Claude Code in current pane${model:+ with model $model}"
  else
    cd "$workdir" && eval "$cmd"
  fi
}

claudeCode.session.save() # <?file:session/agent.context.md> # save current session context to file
{
  local file="${1:-session/agent.context.md}"
  local dir
  dir=$(dirname "$file")

  if [ ! -d "$dir" ]; then
    mkdir -p "$dir"
  fi

  if [ ! -f "$file" ]; then
    # Create template
    cat > "$file" << 'TEMPLATE'
# Agent Context State

**Session**: unknown
**Updated**: $(date -u +%Y-%m-%dT%H:%MZ)
**Role**: unknown

## Current Task
(no task set)

## Team Status
| Pane | Agent | Role | Status |
|------|-------|------|--------|

## Recovery
1. Read this file
2. Read current task file
3. Check agent panes with `tmux capture-pane`
4. Resume work
TEMPLATE
    info.log "Created context template: $file"
  else
    # Update timestamp
    if command -v sed &>/dev/null; then
      local timestamp
      timestamp=$(date -u +%Y-%m-%dT%H:%MZ)
      sed -i '' "s/^\*\*Updated\*\*:.*/\*\*Updated\*\*: $timestamp/" "$file" 2>/dev/null
      info.log "Updated timestamp in: $file"
    fi
  fi

  success.log "Context file ready: $file"
  RESULT="$file"
}

claudeCode.session.recover() # <?file:session/agent.context.md> # read and display recovery context
{
  local file="${1:-session/agent.context.md}"

  if [ ! -f "$file" ]; then
    error.log "Context file not found: $file"
    echo "  Create one with: claudeCode session.save"
    return 1
  fi

  echo "╔════════════════════════════════════════════════════════════╗"
  echo "║              Session Recovery Context                      ║"
  echo "╠════════════════════════════════════════════════════════════╣"
  cat "$file"
  echo ""
  echo "╚════════════════════════════════════════════════════════════╝"

  RESULT="$file"
}

claudeCode.session.recover.completion.file() {
  ls session/*.md 2>/dev/null
}

claudeCode.status() # # show current claude status (no TUI launch)
{
  echo "Claude Code Status"
  echo "=================="

  # Version
  local ver
  ver=$("$CLAUDE_CMD" --version 2>/dev/null | head -1)
  echo "  Version:  ${ver:-unknown}"

  # Binary location
  echo "  Binary:   $CLAUDE_CMD"

  # HIVEMIND_ROLE if set
  if [ -n "${HIVEMIND_ROLE:-}" ]; then
    echo "  Role:     $HIVEMIND_ROLE"
  fi

  # Current pane's Claude process
  if [ -n "$TMUX" ]; then
    local paneTarget
    paneTarget=$(otmux pane.get.target 2>/dev/null)
    if [ -n "$paneTarget" ]; then
      local pid
      pid=$(claudeCode.process.find "$paneTarget" 2>/dev/null)
      if [ -n "$pid" ]; then
        echo "  PID:      $pid (pane $paneTarget)"
        local sid
        sid=$(claudeCode.session.id "$paneTarget" 2>/dev/null)
        [ -n "$sid" ] && echo "  Session:  $sid"
      else
        echo "  Process:  not running in $paneTarget"
      fi
    fi
  fi

  echo ""
}

# ─────────────────────────────────────────────────────────────────────────────
# CONTEXT MONITORING (peer observation)
# ─────────────────────────────────────────────────────────────────────────────

private.claudeCode.context.parse() {
  # Parse context percentage from captured pane content
  # Claude Code TUI shows: "Context left until auto-compact: NN%"
  local content="$1"
  local pct

  # Strip ANSI escape codes first
  local clean
  clean=$(echo "$content" | sed 's/\x1b\[[0-9;]*m//g' | sed 's/\x1b\[[0-9;]*[A-Za-z]//g')

  # Pattern 1: "Context left until auto-compact: NN%"
  pct=$(echo "$clean" | grep -oE 'Context left until auto-compact: [0-9]+%' | grep -oE '[0-9]+' | head -1)
  [ -n "$pct" ] && echo "$pct" && return 0

  # Pattern 2: "context: NN%" or "Context: NN%"
  pct=$(echo "$clean" | grep -oiE 'context:\s*[0-9]+%' | grep -oE '[0-9]+' | head -1)
  [ -n "$pct" ] && echo "$pct" && return 0

  # Pattern 3: Any line with "context" and a percentage nearby
  pct=$(echo "$clean" | grep -iE 'context.*[0-9]+%' | grep -oE '[0-9]+' | head -1)
  [ -n "$pct" ] && echo "$pct" && return 0

  # Pattern 4: Look for percentage near bottom of pane (status bar area)
  pct=$(echo "$clean" | tail -10 | grep -oE '[0-9]+%' | grep -oE '[0-9]+' | head -1)
  [ -n "$pct" ] && echo "$pct" && return 0

  # Debug: log what we captured if parsing fails
  debug.log "context.parse failed. First 5 lines: $(echo "$clean" | head -5)"
  debug.log "context.parse failed. Last 5 lines: $(echo "$clean" | tail -5)"

  return 1
}

claudeCode.context.jsonl() # <?pane> # find .jsonl file for a pane (per-pane), or most recent if no pane
{
  local pane="$1"

  # Per-pane resolution: pane -> session UUID -> JSONL file
  if [ -n "$pane" ]; then
    local sid
    sid=$(claudeCode.session.id "$pane" 2>/dev/null)
    if [ -n "$sid" ]; then
      for projectDir in "$HOME/.claude/projects"/*/; do
        local jsonlFile="${projectDir}${sid}.jsonl"
        if [ -f "$jsonlFile" ]; then
          echo "$jsonlFile"
          return 0
        fi
      done
    fi
  fi

  # Fallback: most recently modified JSONL (for no-pane usage)
  local projectDirs=(
    "$HOME/.claude/projects/-Users-Shared-Workspaces-AI-Claude-components-OOSH-dev-claude"
    "$HOME/.claude/projects/-Users-Shared-Workspaces-AI-Claude"
  )

  local newestFile=""
  local newestTime=0

  for dir in "${projectDirs[@]}"; do
    [ -d "$dir" ] || continue
    while IFS= read -r file; do
      [ -f "$file" ] || continue
      local mtime
      mtime=$(stat -f %m "$file" 2>/dev/null) || continue
      if [ "$mtime" -gt "$newestTime" ]; then
        newestTime="$mtime"
        newestFile="$file"
      fi
    done < <(find "$dir" -maxdepth 1 -name "*.jsonl" 2>/dev/null)
  done

  if [ -n "$newestFile" ]; then
    echo "$newestFile"
    return 0
  fi

  return 1
}
claudeCode.context.jsonl.completion() { :; }

private.claudeCode.context.from.jsonl() {
  # Read context percentage from JSONL token data
  local jsonlFile="$1"
  [ -f "$jsonlFile" ] || return 1

  tail -50 "$jsonlFile" | python3 -c "
import sys, json
last_usage = None
for line in sys.stdin:
    try:
        d = json.loads(line)
        if d.get('type') == 'assistant':
            usage = d.get('message', {}).get('usage', {})
            if usage and 'input_tokens' in usage:
                last_usage = usage
    except: pass
if last_usage:
    input_t = last_usage['input_tokens']
    cache_create = last_usage.get('cache_creation_input_tokens', 0)
    cache_read = last_usage.get('cache_read_input_tokens', 0)
    total = input_t + cache_create + cache_read
    pct = round((total / 200000) * 100, 1)
    remaining = round(100 - pct, 1)
    print(f'{remaining}')
else:
    print('unknown')
" 2>/dev/null
}

claudeCode.context.self() # # read own context % using TMUX_PANE
{
  local myPane
  myPane=$(otmux pane.get.target)
  if [ -z "$myPane" ]; then
    error.log "Not in a tmux pane"
    return 1
  fi
  claudeCode.context.read "$myPane"
}

claudeCode.context.read() # <?pane> # read context % remaining from JSONL token data (falls back to TUI)
{
  local pane="$1"

  # Guard: if pane specified, verify Claude is actually running there
  if [ -n "$pane" ]; then
    if ! claudeCode.process.running "$pane" 2>/dev/null; then
      RESULT="no-claude"
      echo "no-claude"
      return 1
    fi
  fi

  # Try JSONL first (most reliable)
  local jsonlFile
  if [ -n "$pane" ]; then
    # Pane-aware: map pane → session ID → specific JSONL file
    local sid
    sid=$(claudeCode.session.id "$pane" 2>/dev/null)
    if [ -n "$sid" ]; then
      # Search project dirs for this session's JSONL
      local projectDirs=(
        "$HOME/.claude/projects/-Users-Shared-Workspaces-AI-Claude-components-OOSH-dev-claude"
        "$HOME/.claude/projects/-Users-Shared-Workspaces-AI-Claude"
      )
      for dir in "${projectDirs[@]}"; do
        if [ -f "$dir/$sid.jsonl" ]; then
          jsonlFile="$dir/$sid.jsonl"
          break
        fi
      done
    fi
  fi
  # Fallback: global newest ONLY if no pane specified
  # If pane was specified but session lookup failed, don't use global (wrong agent's data)
  if [ -z "$jsonlFile" ] && [ -z "$pane" ]; then
    jsonlFile=$(claudeCode.context.jsonl)
  fi
  if [ -n "$jsonlFile" ] && [ -f "$jsonlFile" ]; then
    local pct
    pct=$(private.claudeCode.context.from.jsonl "$jsonlFile")
    if [ -n "$pct" ] && [ "$pct" != "unknown" ]; then
      RESULT="$pct"
      echo "$pct"
      return 0
    fi
  fi

  # Fall back to TUI scraping if pane specified
  if [ -n "$pane" ]; then
    claudeCode.context.read.tui "$pane"
    return $?
  fi

  RESULT="unknown"
  echo "unknown"
  return 1
}

claudeCode.context.read.tui() # <pane> # read context percentage from Claude Code TUI status bar
{
  local pane="$1"
  if [ -z "$pane" ]; then
    error.log "Usage: claudeCode context.read.tui <pane>"
    return 1
  fi

  # Capture FULL pane content — context info can be anywhere
  local content
  content=$(otmux pane.capture "$pane" 200 2>/dev/null)

  # Also check visible area (bottom of pane where status bar lives)
  local visible
  visible=$(otmux pane.capture.visible "$pane" 2>/dev/null)

  # Try parsing full content first
  local pct
  pct=$(private.claudeCode.context.parse "$content")
  if [ -n "$pct" ]; then
    RESULT="$pct"
    echo "$pct"
    return 0
  fi

  # Try visible area (may have fresher status bar)
  pct=$(private.claudeCode.context.parse "$visible")
  if [ -n "$pct" ]; then
    RESULT="$pct"
    echo "$pct"
    return 0
  fi

  RESULT="unknown"
  echo "unknown"
  return 1
}

claudeCode.context.all() # # show context % for all active sessions
{
  local projectDirs=(
    "$HOME/.claude/projects/-Users-Shared-Workspaces-AI-Claude-components-OOSH-dev-claude"
    "$HOME/.claude/projects/-Users-Shared-Workspaces-AI-Claude"
  )

  local now
  now=$(date +%s)
  local maxAge=3600  # Only show sessions modified in last hour

  for dir in "${projectDirs[@]}"; do
    [ -d "$dir" ] || continue
    while IFS= read -r file; do
      [ -f "$file" ] || continue
      local mtime
      mtime=$(stat -f %m "$file" 2>/dev/null) || continue
      local age=$((now - mtime))
      [ "$age" -gt "$maxAge" ] && continue

      local uuid
      uuid=$(basename "$file" .jsonl)
      local pct
      pct=$(private.claudeCode.context.from.jsonl "$file")
      local minsAgo=$((age / 60))
      echo "${uuid:0:8}... ${pct}% (${minsAgo}m ago)"
    done < <(find "$dir" -maxdepth 1 -name "*.jsonl" 2>/dev/null)
  done
}
claudeCode.context.all.completion() { :; }

claudeCode.context.velocity() # <?paneOrJsonl> # calculate context burn rate — dispatches to byPane or byJsonl
{
  local arg="$1"
  if [ -z "$arg" ]; then
    # No args: use global newest jsonl
    local jsonlFile
    jsonlFile=$(claudeCode.context.jsonl)
    [ -z "$jsonlFile" ] || [ ! -f "$jsonlFile" ] && { echo "unknown"; return 1; }
    private.claudeCode.velocity.calculate "$jsonlFile"
  elif [ -f "$arg" ]; then
    claudeCode.context.velocity.byJsonl "$arg"
  else
    claudeCode.context.velocity.byPane "$arg"
  fi
}
claudeCode.context.velocity.completion.paneOrJsonl() {
  private.claudeCode.complete.panes
}

claudeCode.context.velocity.byPane() # <pane> # calculate velocity by resolving pane → session → jsonl
{
  local pane="$1"
  [ -z "$pane" ] && { error.log "usage: claudeCode context.velocity.byPane <pane>"; return 1; }
  local sid
  sid=$(claudeCode.session.id "$pane" 2>/dev/null)
  if [ -n "$sid" ]; then
    local projectDirs=(
      "$HOME/.claude/projects/-Users-Shared-Workspaces-AI-Claude-components-OOSH-dev-claude"
      "$HOME/.claude/projects/-Users-Shared-Workspaces-AI-Claude"
    )
    local jsonlFile=""
    for dir in "${projectDirs[@]}"; do
      if [ -f "$dir/$sid.jsonl" ]; then
        jsonlFile="$dir/$sid.jsonl"
        break
      fi
    done
    [ -z "$jsonlFile" ] && { echo "unknown"; return 1; }
    private.claudeCode.velocity.calculate "$jsonlFile"
  else
    echo "unknown"
    return 1
  fi
}

claudeCode.context.velocity.byJsonl() # <jsonl> # calculate velocity from a specific jsonl file
{
  local jsonlFile="$1"
  [ -z "$jsonlFile" ] || [ ! -f "$jsonlFile" ] && { echo "unknown"; return 1; }
  private.claudeCode.velocity.calculate "$jsonlFile"
}
claudeCode.context.velocity.byJsonl.completion.jsonl() {
  ls "$HOME/.claude/projects"/*/*.jsonl 2>/dev/null
}

private.claudeCode.velocity.calculate() # <jsonlFile> # parse velocity from jsonl
{
  local jsonlFile="$1"
  [ -z "$jsonlFile" ] || [ ! -f "$jsonlFile" ] && { echo "unknown"; return 1; }

  python3 -c "
import json, sys
from datetime import datetime

messages = []
with open('$jsonlFile') as f:
    for line in f:
        try:
            d = json.loads(line)
            if d.get('type') == 'assistant':
                msg = d.get('message', {})
                usage = msg.get('usage', {})
                if usage and 'input_tokens' in usage:
                    ts = d.get('timestamp', '')
                    input_t = usage['input_tokens']
                    cache_create = usage.get('cache_creation_input_tokens', 0)
                    output_t = usage.get('output_tokens', 0)
                    total = input_t + cache_create + output_t
                    messages.append({'ts': ts, 'tokens': total})
        except:
            pass

if len(messages) < 2:
    print('unknown')
    sys.exit(0)

first = messages[0]
last = messages[-1]

try:
    t1 = datetime.fromisoformat(first['ts'].replace('Z', '+00:00'))
    t2 = datetime.fromisoformat(last['ts'].replace('Z', '+00:00'))
    hours = (t2 - t1).total_seconds() / 3600
    if hours <= 0:
        print('unknown')
        sys.exit(0)

    token_growth = last['tokens'] - first['tokens']
    tokens_per_hour = token_growth / hours

    current = last['tokens']
    max_tokens = 200000
    threshold = int(max_tokens * 0.90)
    remaining = threshold - current

    if tokens_per_hour > 0 and remaining > 0:
        hours_left = remaining / tokens_per_hour
        mins_left = int(hours_left * 60)
        print(f'{int(tokens_per_hour)} tokens/hr | {current} current | ~{mins_left}min until compact')
    elif remaining <= 0:
        print(f'{int(tokens_per_hour)} tokens/hr | {current} current | COMPACT NOW')
    else:
        print(f'{int(tokens_per_hour)} tokens/hr | {current} current')
except Exception as e:
    print('unknown')
" 2>/dev/null

  return 0
}

claudeCode.context.dashboard() # # show velocity dashboard for all active sessions
{
  local projectDirs=(
    "$HOME/.claude/projects/-Users-Shared-Workspaces-AI-Claude-components-OOSH-dev-claude"
    "$HOME/.claude/projects/-Users-Shared-Workspaces-AI-Claude"
  )

  echo "=== Context Velocity Dashboard ==="
  echo "Session  | Usage | Rate    | Time Left"
  echo "---------|-------|---------|----------"

  local now
  now=$(date +%s)

  for dir in "${projectDirs[@]}"; do
    [ -d "$dir" ] || continue
    while IFS= read -r latest; do
      [ -f "$latest" ] || continue

      # Only show recently modified (last hour)
      local mtime
      mtime=$(stat -f %m "$latest" 2>/dev/null) || continue
      local age=$((now - mtime))
      [ "$age" -gt 3600 ] && continue

      local uuid
      uuid=$(basename "$latest" .jsonl)
      local short="${uuid:0:8}"

      # Get velocity info
      local info
      info=$(JSONL_FILE="$latest" python3 -c "
import json, sys, os
from datetime import datetime

jsonl_file = os.environ.get('JSONL_FILE', '')
messages = []
with open(jsonl_file) as f:
    for line in f:
        try:
            d = json.loads(line)
            if d.get('type') == 'assistant':
                usage = d.get('message', {}).get('usage', {})
                if usage and 'input_tokens' in usage:
                    total = usage['input_tokens'] + usage.get('cache_creation_input_tokens', 0) + usage.get('cache_read_input_tokens', 0)
                    messages.append({'ts': d.get('timestamp', ''), 'tokens': total})
        except: pass

if len(messages) < 2:
    print('- | - | -')
    sys.exit(0)

first, last = messages[0], messages[-1]
try:
    t1 = datetime.fromisoformat(first['ts'].replace('Z', '+00:00'))
    t2 = datetime.fromisoformat(last['ts'].replace('Z', '+00:00'))
    hours = (t2 - t1).total_seconds() / 3600
    if hours <= 0:
        print(f\"{round(last['tokens']/200000*100,1)}% | - | -\")
        sys.exit(0)
    rate = int((last['tokens'] - first['tokens']) / hours)
    remaining = 180000 - last['tokens']
    mins = int(remaining / rate * 60) if rate > 0 and remaining > 0 else 0
    pct = round(last['tokens'] / 200000 * 100, 1)
    status = 'COMPACT!' if remaining <= 0 else f'{mins}min'
    print(f\"{pct}% | {rate}/hr | {status}\")
except:
    print('- | - | -')
" 2>/dev/null)

      echo "$short | $info"
    done < <(find "$dir" -maxdepth 1 -name "*.jsonl" 2>/dev/null)
  done
}
claudeCode.context.dashboard.completion() { :; }

claudeCode.context.check() # <pane> # check context health: percentage, velocity, log, alert if low
{
  local pane="$1"

  if [ -z "$pane" ]; then
    error.log "Usage: claudeCode context.check <pane>"
    return 1
  fi

  # 1. Get context percentage
  local pct
  pct=$(claudeCode.context.read "$pane" 2>/dev/null)
  [ -z "$pct" ] && pct="unknown"

  # 2. Get velocity
  local velocity ttc tokensHr
  velocity=$(claudeCode.context.velocity.byPane "$pane" 2>/dev/null)
  tokensHr=$(echo "$velocity" | grep -oE '^[0-9]+ tokens/hr' || echo "-")
  ttc=$(echo "$velocity" | grep -oE '~[0-9]+min' || echo "-")

  # 3. Determine state
  local state="ok"
  if [ "$pct" = "unknown" ]; then
    state="unknown"
  elif [ "$pct" -le 10 ] 2>/dev/null; then
    state="CRITICAL"
  elif [ "$pct" -le 25 ] 2>/dev/null; then
    state="LOW"
  fi

  # 4. Log to burn log
  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  if [ -n "$workspace" ]; then
    local logfile="${workspace}/session/context-burn-log.md"
    local ts
    ts=$(date '+%H:%M')
    if [ ! -f "$logfile" ]; then
      echo "| Time | Pane | % | State | Velocity | TTC |" > "$logfile"
      echo "|------|------|---|-------|----------|-----|" >> "$logfile"
    fi
    echo "| $ts | $pane | ${pct}% | $state | $tokensHr | $ttc |" >> "$logfile"
  fi

  # 5. Alert if low
  if [ "$state" = "LOW" ]; then
    warn.log "$pane: ${pct}% context — alert peer"
  elif [ "$state" = "CRITICAL" ]; then
    warn.log "$pane: ${pct}% context — CRITICAL, triggering compact"
    claudeCode.context.alert "$pane" 10
  fi

  # 6. Output summary
  console.log "$pane: ${pct}% | $tokensHr | $ttc | $state"
  create.result 0 "$pct"
  return $(result)
}

claudeCode.context.alert() # <pane> <?threshold:20> # alert if context below threshold; sends warning to pane
{
  local pane="$1"
  local threshold="${2:-20}"

  if [ -z "$pane" ]; then
    error.log "Usage: claudeCode context.alert <pane> <?threshold>"
    return 1
  fi

  claudeCode.context.read "$pane" > /dev/null
  local pct="$RESULT"

  # Can't alert if context is unknown
  if [ "$pct" = "unknown" ]; then
    debug.log "Cannot alert $pane — context unknown"
    return 1
  fi

  if [ "$pct" -le "$threshold" ] 2>/dev/null; then
    warn.log "Agent at $pane has ${pct}% context remaining"
    "$OOSH_DIR/otmux" send.enter "$pane" "CONTEXT: ${pct}% — save state now"
    return 0
  fi

  return 1
}

claudeCode.recover() # <pane> # post-compact recovery: read identity files, check peer, resume work
{
  local pane="$1"

  if [ -z "$pane" ]; then
    error.log "Usage: claudeCode recover <pane>"
    return 1
  fi

  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  if [ -z "$workspace" ]; then
    error.log "Cannot determine workspace root"
    return 1
  fi

  # Resolve role from registry
  local role
  role=$("$OOSH_DIR/hiveMind" resolve.reverse "$pane" 2>/dev/null)
  if [ -z "$role" ]; then
    local reg="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
    [ -f "$reg" ] && role=$(grep "^${pane}|" "$reg" 2>/dev/null | cut -d'|' -f2)
  fi

  if [ -z "$role" ]; then
    warn.log "Cannot resolve role for $pane — skipping identity files"
  fi

  # Step 1: Check identity files exist
  local learningsFile="${workspace}/session/agents/${role}/learnings.md"
  local contextFile="${workspace}/session/agents/${role}/context.md"
  local bootFile="${workspace}/session/boot/${role}.md"

  local filesFound=0
  if [ -n "$role" ]; then
    [ -f "$bootFile" ] && { info.log "Boot file: $bootFile"; filesFound=$((filesFound + 1)); }
    [ -f "$learningsFile" ] && { info.log "Learnings: $learningsFile"; filesFound=$((filesFound + 1)); }
    [ -f "$contextFile" ] && { info.log "Context: $contextFile"; filesFound=$((filesFound + 1)); }
  fi

  # Step 2: Send boot file read to the recovering pane
  if [ -f "$bootFile" ]; then
    local relBoot="session/boot/${role}.md"
    "$OOSH_DIR/otmux" send "$pane" "Read ${relBoot}" Enter
    info.log "Sent boot file to $pane"
  elif [ -f "$contextFile" ]; then
    local relCtx="session/agents/${role}/context.md"
    "$OOSH_DIR/otmux" send "$pane" "Read ${relCtx}" Enter
    info.log "Sent context file to $pane"
  else
    warn.log "No boot or context file found for $role"
  fi

  # Step 3: Check context health
  local pct
  pct=$(claudeCode.context.read "$pane" 2>/dev/null)
  info.log "$pane context: ${pct:-unknown}%"

  # Step 4: Verify agent responded (wait briefly, then capture)
  sleep 3
  local verify
  verify=$("$OOSH_DIR/otmux" pane.capture "$pane" 10 2>/dev/null)
  if echo "$verify" | grep -qE 'Read|boot|context|❯'; then
    console.log "recover: $role at $pane — resumed ($filesFound files, ${pct:-?}% context)"
    create.result 0 "recovered"
  else
    warn.log "recover: $role at $pane — may not have resumed"
    create.result 1 "unverified"
  fi
  return $(result)
}

claudeCode.usage()
{
  local this=${0##*/}
  echo "You started"
  echo "$0

  claudeCode - Claude Code wrapper for oosh
  Makes cryptic --flags easy to remember!

  Usage:
  $this: command     Description
  ─────────────────────────────────────────────────────
  SESSION MANAGEMENT:
      list          show session picker (--resume)
      join <name>   resume specific session (--resume <name>)
      join.byID     resume by session UUID
      join.byName   resume by role name (sessions.env lookup)
      join.byPane   resume by resolving pane → UUID
      fork <uuid>   fork session with new ID (cross-machine)
      continue      continue most recent session (--continue)
      c             shorthand for continue
      new           start fresh conversation

  OUTPUT MODES:
      print <msg>   non-interactive output (--print)
      p <msg>       shorthand for print
      json <msg>    output as JSON
      pipe          read from stdin (-p)

  MODELS:
      opus <msg>    use opus model
      sonnet <msg>  use sonnet model
      haiku <msg>   use haiku model
      model <m>     specify model (--model)

  PERMISSIONS:
      yolo <msg>    skip all prompts (--dangerously-skip-permissions)
      dangerously   same as yolo

  CONFIGURATION:
      config        open settings
      doctor        check setup
      init          initialize directory
      mcp           manage MCP servers

  INSTALLATION:
      install       install claude code from web for linux
      uninstall     remove claude code installation
      update        update claude code to latest version
      login         authenticate with claude
      logout        log out from claude

  AGENT SUPPORT:
      agent.start       start claude code in pane
      session.save      save context to file
      session.recover   read recovery context
      process.find      find Claude PID for a tmux pane
      process.running   check if Claude is running in pane
      session.id        get session UUID for a tmux pane

  CONTEXT MONITORING:
      context.self            read own context % (auto-detects pane)
      context.read <pane>     read context % from TUI status bar
      context.alert <pane>    alert if context below threshold (default 20%)
      context.velocity        token burn rate (dispatches to byPane/byJsonl)
      context.velocity.byPane   velocity by resolving pane → session → jsonl
      context.velocity.byJsonl  velocity from a specific jsonl file

  OTHER:
      verbose       verbose output
      maxTurns <n>  limit turns
      help          show claude --help
      v             show version
  ─────────────────────────────────────────────────────

  Examples:
    $this list                    # pick a session to resume
    $this continue                # continue last session
    $this join my-feature         # resume 'my-feature' session
    $this opus 'explain this'     # use opus model
    $this print 'quick question'  # get answer and exit
    $this yolo 'fix all tests'    # no permission prompts
    $this session.save            # save agent context
    $this session.recover         # read recovery context
    cat file.py | $this pipe 'review this'
  "
}

# ─────────────────────────────────────────────────────────────────────────────
# PARAMETER COMPLETIONS (shared across all methods)
# ─────────────────────────────────────────────────────────────────────────────

claudeCode.parameter.completion.pane() {
  tmux list-panes -a -F "#{session_name}:#{window_index}.#{pane_index}" 2>/dev/null
}

claudeCode.parameter.completion.sessionId() {
  private.claudeCode.complete.sessionIds
}

claudeCode.parameter.completion.model() {
  echo "sonnet"
  echo "opus"
  echo "haiku"
}

claudeCode.parameter.completion.workdir() {
  compgen -d "$1"
}

claudeCode.parameter.completion.file() {
  compgen -f "$1"
}

claudeCode.parameter.completion.path() {
  compgen -d "$1"
}

claudeCode.start()
{
  #echo "sourcing init"
  source this

  if [ -z "$1" ]; then
    claudeCode.usage
    return 0
  fi

  this.start "$@"
}

claudeCode.start "$@"
