#!/usr/bin/env bash
set -euo pipefail

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Whatever name the user actually typed. `agentlas` is a symlink to this same
# runner, and answering "hephaestus:" to someone who ran `agentlas` is the
# half-renamed confusion this symlink exists to remove.
self="$(basename "${BASH_SOURCE[0]}")"
# The host spelling is compatibility syntax. Core resolves it to an internal
# commandId after Python starts, so aliases and runtime adapters never need to
# duplicate semantic routing rules.
export AGENTLAS_COMMAND_NAME="${1:-}"
export PYTHONUTF8="${PYTHONUTF8:-1}"
export PYTHONIOENCODING="${PYTHONIOENCODING:-utf-8}"
export PYTHONDONTWRITEBYTECODE=1
source "$root/bin/agentlas-python-cache-boundary"
if ! agentlas_export_python_cache_boundary; then
  echo "$self: could not establish a safe external Python cache directory." >&2
  exit 78
fi

resolve_python() {
  local candidate
  for candidate in \
    "${HEPHAESTUS_PYTHON:-}" \
    "$root/bin/python3" \
    python3 \
    python
  do
    [[ -n "$candidate" ]] || continue
    if "$candidate" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)' >/dev/null 2>&1; then
      printf '%s\n' "$candidate"
      return 0
    fi
  done
  if command -v py >/dev/null 2>&1 && py -3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)' >/dev/null 2>&1; then
    printf '%s\n' "py -3"
    return 0
  fi
  return 1
}

HEPHAESTUS_PY="$(resolve_python || true)"
# The interpreter may be a native Windows python launched from MSYS bash, which
# cannot read a POSIX root. Convert once: this value is both PYTHONPATH and the
# sys.path entry the bootstrap below inserts.
root_native="$(agentlas_native_path "$root")"
path_sep="$(agentlas_path_sep)"

run_python_module() {
  if [[ -z "$HEPHAESTUS_PY" ]]; then
    echo "$self: Python 3.9+ not found. On macOS/Linux install python3 (python.org or your package manager); on Windows install it from python.org or run 'py -3' once. Then rerun hephaestus doctor." >&2
    exit 127
  fi
  # shellcheck disable=SC2086
  HEPHAESTUS_RUNTIME_ROOT="$root_native" PYTHONPATH="$root_native${PYTHONPATH:+$path_sep$PYTHONPATH}" $HEPHAESTUS_PY -c 'import os, runpy, sys; cwd=os.getcwd(); root=os.environ["HEPHAESTUS_RUNTIME_ROOT"]; sys.path=[p for p in sys.path if p not in ("", cwd, root)]; sys.path.insert(0, root); sys.argv=sys.argv[1:]; runpy.run_module(sys.argv[0], run_name="__main__", alter_sys=True)' "$@"
}

upload_question() {
  cat <<'EOF'
Cloud에 업로드 할까요? 다른사람들이 볼 수 없어요.
Upload to Cloud? Other people cannot see it.

Agentlas Hub에 업로드 할까요? 다른 사람들이 빌려 쓸 수 있어요.
Upload to Agentlas Hub? Other people can borrow it.

1) Cloud
2) Agentlas Hub
EOF
}

# Asked only after the Hub is chosen. A private Cloud save is not listed and
# nobody can hire it, so there is nothing for a price to apply to.
#
# Every field is optional and blank means "not sold" — NOT zero. An agent with
# no price at all is callable for free, which is a supported state and where
# every agent published before pricing existed lives. So this must never block
# the upload.
price_question() {
  cat <<'EOF'

값을 정하시겠어요? 비워 두고 Enter를 누르면 그 항목은 팔지 않습니다.
Set a price? Press Enter to leave one out — that kind is simply not sold.

  1) 빌리기 / Rent      워크오더 1건 · 24시간   1-100 크레딧
  2) 인제스트 / Ingest   프로젝트 1개 · 하루     1-2000 크레딧
  3) 포크 / Fork        사본 1개 · 1회         1 크레딧 이상

전부 비워 두면 무료로 불립니다. 나중에 agentlas.cloud 수익 페이지에서 정해도 됩니다.
Leave them all blank and it stays free to call. You can price it later on the web.
EOF
}

# Read one optional price. Empty is accepted and means "not sold"; a non-number
# is re-asked rather than silently dropped, because a typo that becomes "free"
# is the expensive kind of mistake here.
read_price() {
  local label="$1" flag="$2" value
  while true; do
    printf '%s: ' "$label" >&2
    read -r value || return 0
    value="$(printf '%s' "$value" | tr -d '[:space:]')"
    [[ -z "$value" ]] && return 0
    if [[ "$value" =~ ^[0-9]+$ ]]; then
      printf '%s %s' "$flag" "$value"
      return 0
    fi
    echo "  숫자만 입력하세요 (비우려면 Enter). Numbers only, or Enter to skip." >&2
  done
}

resolve_upload_folder() {
  local arg candidate
  for arg in "$@"; do
    candidate="${arg#file://}"
    candidate="${candidate%\"}"
    candidate="${candidate#\"}"
    if [[ -d "$candidate" ]]; then
      (cd "$candidate" && pwd)
      return 0
    fi
    if [[ -d "$PWD/$candidate" ]]; then
      (cd "$PWD/$candidate" && pwd)
      return 0
    fi
  done
  return 1
}

run_upload_gate() {
  local target choice visibility="" arg
  local -a folder_args=() publish_args=()
  set_upload_visibility() {
    local requested="$1" normalized
    case "$requested" in
      cloud|private|private-link) normalized="private-link" ;;
      hub|marketplace|public|agentlas-hub) normalized="marketplace" ;;
      *)
        echo "Unknown upload visibility: $requested. Use private-link or marketplace." >&2
        return 2
        ;;
    esac
    if [[ -n "$visibility" && "$visibility" != "$normalized" ]]; then
      echo "Contradictory upload destinations: both $visibility and $normalized were requested." >&2
      return 2
    fi
    visibility="$normalized"
  }
  while [[ "$#" -gt 0 ]]; do
    arg="$1"
    case "$arg" in
      --visibility)
        if [[ "$#" -lt 2 || "${2:-}" == --* ]]; then
          echo "Missing value for --visibility. Use private-link or marketplace." >&2
          return 2
        fi
        set_upload_visibility "${2:-}" || return $?
        shift 2
        ;;
      --visibility=*)
        set_upload_visibility "${arg#--visibility=}" || return $?
        shift
        ;;
      --cloud|cloud|private|private-link)
        set_upload_visibility private-link || return $?
        shift
        ;;
      --hub|hub|marketplace|public|agentlas-hub)
        set_upload_visibility marketplace || return $?
        shift
        ;;
      --dry-run|--no-open)
        publish_args+=("$arg")
        shift
        ;;
      --slug|--base-url|--expected-package-hash|--expected-upload-receipt|--overwrite-cloud-id|--rent-credits|--ingest-credits|--fork-credits)
        if [[ "$#" -lt 2 || "${2:-}" == --* ]]; then
          echo "Missing value for $arg." >&2
          return 2
        fi
        publish_args+=("$arg" "${2:-}")
        shift 2
        ;;
      --slug=*|--base-url=*|--expected-package-hash=*|--expected-upload-receipt=*|--overwrite-cloud-id=*|--rent-credits=*|--ingest-credits=*|--fork-credits=*)
        publish_args+=("$arg")
        shift
        ;;
      *)
        folder_args+=("$arg")
        shift
        ;;
    esac
  done
  unset -f set_upload_visibility
  if [[ "${#folder_args[@]}" -gt 1 ]]; then
    echo "Choose exactly one upload folder." >&2
    return 2
  fi
  if [[ -n "${visibility:-}" ]]; then
    if ! target="$(resolve_upload_folder ${folder_args[@]+"${folder_args[@]}"})"; then
      echo "업로드 대상 에이전트 폴더를 찾지 못했습니다. /hep-upload <agent-folder> --visibility <private-link|marketplace> 형태로 다시 실행하세요." >&2
      return 2
    fi
    run_python_module agentlas_cloud publish "$target" --visibility "$visibility" ${publish_args[@]+"${publish_args[@]}"}
    return $?
  fi
  upload_question
  if [[ ! -t 0 ]]; then
    echo '{"status":"input_required","error":"upload_destination_required","performed":false}' >&2
    echo "No upload performed. Choose Cloud or Agentlas Hub in the chat, then run one explicit command:" >&2
    echo "  hep-upload <agent-folder> --visibility private-link   # Cloud" >&2
    echo "  hep-upload <agent-folder> --visibility marketplace    # Agentlas Hub" >&2
    echo "For the Hub, also ask what it should charge and pass what they answer:" >&2
    echo "  --rent-credits <1-100>      per work order (24h lease)" >&2
    echo "  --ingest-credits <1-2000>   per project per day" >&2
    echo "  --fork-credits <1+>         one copy, once" >&2
    echo "Omit a flag and that kind is not sold. Omit all three and the agent is free to call." >&2
    return 3
  fi
  printf 'Select 1 or 2: ' >&2
  read -r choice
  if ! target="$(resolve_upload_folder ${folder_args[@]+"${folder_args[@]}"})"; then
    echo "업로드 대상 에이전트 폴더를 찾지 못했습니다. /hep-upload <agent-folder> 형태로 다시 실행하세요." >&2
    return 2
  fi
  case "$(printf '%s' "$choice" | tr '[:upper:]' '[:lower:]')" in
    1|cloud|c)
      run_python_module agentlas_cloud publish "$target" --visibility private-link ${publish_args[@]+"${publish_args[@]}"}
      ;;
    2|hub|agentlas\ hub|agentlas|h)
      # Prices are only asked when they were not already given on the command
      # line, so a scripted call is never interrupted by a prompt.
      if [[ ! " ${publish_args[*]-} " =~ (--rent-credits|--ingest-credits|--fork-credits) ]]; then
        price_question
        local -a price_args=()
        # shellcheck disable=SC2207
        price_args+=($(read_price "빌리기 / Rent (1-100)" --rent-credits))
        # shellcheck disable=SC2207
        price_args+=($(read_price "인제스트 / Ingest (1-2000)" --ingest-credits))
        # shellcheck disable=SC2207
        price_args+=($(read_price "포크 / Fork (1+)" --fork-credits))
        publish_args+=(${price_args[@]+"${price_args[@]}"})
      fi
      run_python_module agentlas_cloud publish "$target" --visibility marketplace ${publish_args[@]+"${publish_args[@]}"}
      ;;
    *)
      echo "Upload cancelled. Choose 1 for Cloud or 2 for Agentlas Hub." >&2
      return 2
      ;;
  esac
}

usage() {
  cat <<'EOF' | sed "s|bin/hephaestus|$self|g"
Usage:
  bin/hephaestus --version
  bin/hephaestus career-graph status|ingest|verify|trace|public-card [--project <dir>]
  bin/hephaestus career-graph query "<question>" [--project <dir>]
  bin/hephaestus ontology [--gui] [--no-open] [project-dir]
  bin/hephaestus ontology add <path> [--project <dir>] [--kind company|project|personal] [--scope public|internal|private]
  bin/hephaestus wizard <agent-folder> [--name <name>]
  bin/hephaestus security scan <agent-folder> [--strict] [--acknowledge-warn] [--llm-judgment <path>]
  bin/hephaestus runtime bundle <agent-folder>
  bin/hephaestus runtime read-agent-file <agent-folder> <path>
  bin/hephaestus contract scaffold <agent-folder> [--mode single|team|package]
  bin/hephaestus contract verify <agent-folder> [--mode single|team|package]
  bin/hephaestus contract prompt [--mode single|team|package]
  bin/hephaestus session [inspect|normalize|validate|preview|merge|ir|compile|promote] [--input <session.jsonl>] [--global-agent]
  bin/hephaestus package <agent-folder> [--visibility marketplace|private-link]
  bin/hephaestus publish <agent-folder> --visibility marketplace|private-link [--dry-run] [--expected-package-hash <sha256>] [--expected-upload-receipt <sha256>] [--overwrite-cloud-id <id>]
  bin/hephaestus field-test
	  bin/hephaestus auth status|login|ensure|logout [--base-url <url>]
  bin/hephaestus doctor
  bin/hephaestus hep-update [--check]
  bin/hephaestus global install|status|remove [--target codex] [--target claude] [--target antigravity]
  bin/hephaestus plugins list [--project <dir>]
  bin/hephaestus plugins resolve "<need>" [--project <dir>] [--no-hub]
  bin/hephaestus project ensure|status [--project <dir>]
  bin/hephaestus meta-agent "<request>"
  bin/hephaestus hep-build "<request>"             # build/create/package surface
  bin/hephaestus build "<request>"                 # shorthand for hep-build
  bin/hephaestus network init|status|reindex|bench
  bin/hephaestus network add-source <path> | remove-source <path>
  bin/hephaestus network grant <capability> --target <id> [--scope ...]
  bin/hephaestus workforce goal-bind <goal-id> <prepared-plan.json> [--project <dir>]
  bin/hephaestus workforce goal-status [--goal-id <id>] [--project <dir>]
  bin/hephaestus workforce goal-runtime [--goal-id <id>] [--project <dir>]
  bin/hephaestus workforce goal-turn <goal-id> <turn-id> reuse|recruit|local-only|blocked|standby [--project <dir>]
  bin/hephaestus workforce goal-complete <goal-id> --explicit [--project <dir>]
  bin/hephaestus cards lint [path] | cards migrate <root> --tier restricted|private|plugin|local
  bin/hephaestus ao lint [dir] | ao graph [--agent <agent>] [dir] | ao query "<query>" [dir]
  bin/hephaestus ao plan <from> <to> [--max-depth N] [dir]
  bin/hephaestus ao diff [dir] | ao migrate [dir] [--no-write] [--overwrite]
  bin/hephaestus route "<request>" [--project <dir>] [--no-hub] [--approve-hub] [--hub-only] [--scope network|cloud] [--caller <agent-id>]
  bin/hephaestus local-gui "<shortcut>" [--detach] [--no-open]   # restore/open packaged GUI shortcuts
  bin/hephaestus hep-network "<request>"           # Local + owner Cloud + public Hub temporary TF
  bin/hephaestus hep-local "<request>"             # registered Local only
  bin/hephaestus hep-hub "<request>"               # public Hub only
  bin/hephaestus stormbreaker run "<request>" [--background] [--executor-command <cmd>] [--research-evidence] [--research-loadout auto|safe|public-web|social|browser|full|recommended]  # auto-run pipeline packets
  bin/hephaestus hep-storm "<request>" [--background] [--executor-command <cmd>] [--research-evidence] [--research-loadout auto|safe|public-web|social|browser|full|recommended]  # shorthand
  bin/hephaestus hep-cloud "<request>"              # owner Cloud only (보관함)
  bin/hephaestus hep-search "<request>" [--limit 10]     # show cloud + Hub candidates only
  bin/hephaestus hep-browser <url-or-query> [--setup|--check]  # use Agentlas browser hardpoint first
  bin/hephaestus research doctor|status|credentials|modules|armory|profile|bridge-contract|bridge-check|platform-contract|platform-check|loadouts | research plan <url> [--loadout public-web|browser] [--depth deep] | research gather "<query>" [--variant docs] [--follow-results 3] | research search "<query>" [--variant reddit] [--loadout safe|public-web|social] | research read <url> [--loadout public-web|browser] [--depth deep] [--max-weight browser_heavy]  # lightweight Research Engine
  bin/hephaestus hep-call "agent-a,agent-b" "<context>"  # prepare named Hub/cloud agents
  bin/hephaestus hep-upload <agent-folder>          # ask Cloud vs Hub before upload
  bin/hephaestus hep-global install                 # install Codex/Claude/Antigravity global router prompt blocks
  bin/hephaestus mcp serve              # stdio MCP server (hephaestus_route tool)

Examples:
  bin/hephaestus career-graph ingest --project .
  bin/hephaestus career-graph query "release failures" --project .
  bin/hephaestus career-graph public-card --write --project .
  bin/hephaestus ontology
  bin/hephaestus ontology --gui .
  bin/hephaestus ontology add ./company-docs --kind company --scope private
  bin/hephaestus wizard ./some-agent --name instagram-operator
  bin/hephaestus security scan ./some-agent --strict
  bin/hephaestus runtime bundle ./some-agent
EOF
}

if [[ "${1:-}" == "--version" || "${1:-}" == "-V" || "${1:-}" == "version" ]]; then
  release=""
  if [[ -f "$root/RELEASE" ]]; then
    release="$(tr -d '\r\n' < "$root/RELEASE")"
  elif [[ -f "$root/scripts/install-all-runtimes.sh" ]]; then
    release="$(sed -n 's/.*HEPHAESTUS_REF:-\(v[0-9.]*\)}.*/\1/p' "$root/scripts/install-all-runtimes.sh" | head -1)"
  fi
  if [[ -z "$release" ]]; then
    echo "$self: release metadata is missing." >&2
    exit 1
  fi
  printf '%s %s\n' "$self" "${release#v}"
  exit 0
fi

if [[ "${1:-}" == "" || "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
  usage
  exit 0
fi

command="$1"
shift

case "$command" in
	  career-graph|career_graph)
	    run_python_module career_graph "$@"
	    ;;
	  ontology)
	    if [[ "${1:-}" == "add" ]]; then
	      shift
	      run_python_module ontology sources add "$@"
	      exit $?
	    fi

    open_flag=1
    if [[ "${1:-}" == "--gui" ]]; then
      shift
    fi
    if [[ "${1:-}" == "--no-open" ]]; then
      open_flag=0
      shift
    fi
	    project="${1:-.}"
	    if [[ "$open_flag" == "1" ]]; then
	      run_python_module ontology gui "$project"
	    else
	      run_python_module ontology gui "$project" --no-open
	    fi
	    ;;
	  wizard)
	    run_python_module agentlas_cloud wizard "$@"
	    ;;
	  security)
	    run_python_module agentlas_cloud security "$@"
	    ;;
  runtime)
    runtime_sub="${1:-}"
    shift || true
    case "$runtime_sub" in
	      bundle)
	        run_python_module agentlas_cloud bundle "$@"
	        ;;
	      read-agent-file)
	        run_python_module agentlas_cloud read-agent-file "$@"
	        ;;
      *)
        echo "$self: unknown runtime command: $runtime_sub" >&2
        usage >&2
        exit 2
        ;;
    esac
    ;;
	  field-test)
	    run_python_module agentlas_cloud field-test
	    ;;
	  auth)
	    run_python_module agentlas_cloud auth "$@"
	    ;;
	  doctor)
	    run_python_module agentlas_cloud doctor "$@"
	    ;;
	  update|hep-update)
	    run_python_module agentlas_cloud hep-update "$@"
	    ;;
	  global|hep-global|hephaestus-global)
	    run_python_module agentlas_cloud global "$@"
	    ;;
	  orch|hep-orch|hephaestus-orch|agentlas-orch)
	    # Which model runs the orchestrator, and which runs the workers.
	    # The allocator reads this policy before every role-split invocation;
	    # without it every worker inherited the orchestrator's frontier model.
	    exec "$(dirname "${BASH_SOURCE[0]}")/agentlas-one" orch "$@"
	    ;;
	  plugins)
	    run_python_module agentlas_cloud plugins "$@"
	    ;;
	  meta-agent|hep-build|hephaestus-build|Hephaestus-build|build)
	    # `session` is the deterministic local source/IR boundary used by the
	    # host-LM /hep-build session flow. Other build requests remain host-LLM
	    # orchestration and must not silently fall back to this CLI.
	    if [[ "${1:-}" == "session" ]]; then
	      shift
	      run_python_module agentlas_cloud session "$@"
	      exit $?
	    fi
	    request="${*:-Create or package an Agentlas-compatible agent.}"
	    escaped_request="${request//\\/\\\\}"
	    escaped_request="${escaped_request//\"/\\\"}"
	    escaped_request="${escaped_request//$'\n'/\\n}"
	    printf '{"status":"error","error":"host_llm_required","command":"hep-build","request":"%s","message":"hep-build requires a connected host LLM; run /hep-build in an installed Hephaestus host adapter"}\n' "$escaped_request" >&2
	    exit 3
	    ;;
	  session)
	    run_python_module agentlas_cloud session "$@"
	    ;;
	  command|network|cards|ao|route|mcp|workforce|local-gui|package|publish|project|context|contract|feature-map)
	    # `contract` was the one subcommand the Python CLI exposed that this
	    # dispatcher never listed. It fell through to the natural-language
	    # router, which read the package path as a private path and refused
	    # (`unsafe_route_input`) — so the only machine-readable list of what a
	    # package is still missing was unreachable from every runtime adapter.
	    run_python_module agentlas_cloud "$command" "$@"
	    ;;
	  hep-network|hephaestus-network|hephaests-network)
	    if [[ "${HEPHAESTUS_LEGACY_ROUTER:-0}" == "1" ]]; then
	      # Explicit compatibility/debug opt-in only. The normal command path
	      # remains host-LLM staffing through the Workforce MCP contract below.
	      if [[ "${HEPHAESTUS_NETWORK_GUI_SHORTCUTS:-1}" != "0" ]]; then
	        set +e
	        gui_args=("$@" "--detach")
	        if [[ "${HEPHAESTUS_NETWORK_GUI_NO_OPEN:-0}" == "1" ]]; then
	          gui_args+=("--no-open")
	        fi
	        gui_output="$(run_python_module agentlas_cloud local-gui "${gui_args[@]}" 2>/dev/null)"
	        gui_status=$?
	        set -e
	        if [[ "$gui_status" == "0" ]]; then
	          printf '%s\n' "$gui_output"
	          exit 0
	        elif [[ "$gui_status" != "4" ]]; then
	          printf '%s\n' "$gui_output"
	          exit "$gui_status"
	        fi
	      fi
	      if [[ "${HEPHAESTUS_NETWORK_AUTO_RUN:-1}" == "0" ]]; then
	        run_python_module agentlas_cloud route "$@" --hub-only --scope network
	      else
	        run_python_module agentlas_cloud route "$@" --hub-only --scope network --auto-run --background
	      fi
	      exit $?
	    fi
	    printf '%s\n' '{"action":"workforce_orchestration_required","status":"host_llm_required","sourceScope":"network","sources":["local","cloud","hub"],"decisionOwner":"host_llm","legacyRouter":"disabled","detail":"Run /hep-network or @Hephaestus inside an MCP-capable host. Core federates source menus; the host LLM selects the team."}'
	    exit 3
	    ;;
	  connect|hep-connect|hephaestus-connect|agentlas-connect)
	    # Telegram Connect is a guided Desktop flow (registry: agentlas.connect is
	    # identity_only, no installed entrypoint). Until 2026-09-05 this verb fell
	    # through to the generic router and printed a route menu — a dead end for
	    # the person who typed it. A raised stop must carry its way out.
	    printf '%s\n' '{"action":"desktop_connect_required","status":"host_llm_required","command":"hep-connect","surface":"desktop","detail":"Telegram Connect runs in Agentlas Desktop → Connect (choose the agent, team, or group; connect the bot; pair a chat; send a test message). From Claude Code or Codex type /hep-connect for the guided flow. This shell form only points the way."}' >&2
	    exit 3
	    ;;
	  hep-local|hephaestus-local)
	    printf '%s\n' '{"action":"workforce_orchestration_required","status":"host_llm_required","sourceScope":"local","sources":["local"],"decisionOwner":"host_llm","legacyRouter":"disabled"}'
	    exit 3
	    ;;
	  hep-hub|hephaestus-hub)
	    printf '%s\n' '{"action":"workforce_orchestration_required","status":"host_llm_required","sourceScope":"hub","sources":["hub"],"decisionOwner":"host_llm","legacyRouter":"disabled"}'
	    exit 3
	    ;;
		  stormbreaker)
		    run_python_module agentlas_cloud stormbreaker "$@"
		    ;;
		  hep-storm|hephaestus-storm|storm)
		    # The user-facing Storm shortcut follows the same Hub-first contract as
		    # hep-network. Operators who intentionally want local-card debug routing
		    # can still use the lower-level `stormbreaker run` command directly.
		    # set -u 아래에서 빈 배열을 "${a[@]}" 로 펴면 bash 3.2(맥 기본)가
		    # unbound variable 로 죽는다. 인자 없이 부른 사용자가 셸 내부 오류를
		    # 보게 되므로, 이 파일이 다른 곳에서 쓰는 것과 같은 보호 관용구를 쓴다.
		    storm_args=("$@")
		    storm_hub_only=1
		    for storm_arg in ${storm_args[@]+"${storm_args[@]}"}; do
		      if [[ "$storm_arg" == "--no-hub" ]]; then
		        storm_hub_only=0
		        break
		      fi
		    done
		    if [[ "$storm_hub_only" == "1" ]]; then
		      storm_args+=("--hub-only")
		    fi
		    run_python_module agentlas_cloud stormbreaker run ${storm_args[@]+"${storm_args[@]}"}
		    ;;
		  search|hep-search|hephaestus-search|Hephaestus-search)
		    run_python_module agentlas_cloud search "$@"
		    ;;
		  browser|hep-browser|hephaestus-browser|Hephaestus-browser)
		    run_python_module agentlas_cloud hep-browser "$@"
		    ;;
		  research)
		    run_python_module agentlas_cloud research "$@"
		    ;;
		  call|hep-call|hephaestus-call|Hephaestus-call)
		    run_python_module agentlas_cloud call "$@"
		    ;;
		  login|hep-login|agentlas-login)
		    # 로그인 전용 진입 — 브라우저 로그인 창을 열고 완료를 기다린다.
		    run_python_module agentlas_cloud auth login "$@"
		    ;;
		  cloud|hep-cloud|hephaestus-cloud)
		    printf '%s\n' '{"action":"workforce_orchestration_required","status":"host_llm_required","sourceScope":"cloud","sources":["cloud"],"decisionOwner":"host_llm","legacyRouter":"disabled"}'
		    exit 3
		    ;;
      upload|hep-upload|hephaestus-upload)
        run_upload_gate "$@"
        ;;
  -*)
    echo "$self: unknown command: $command" >&2
    usage >&2
    exit 2
    ;;
	  *)
	    # Natural-language shorthand: hephaestus "<request>" routes through the network router.
	    #
	    # A bare single word is a typo, not a request. This branch used to swallow
	    # every unrecognised token — `hephaestus login`, `status`, `doctorr`, `init`
	    # — turning it into a Hub search that exited 0 after 1-4s with no notice
	    # that the command did not exist. Two things broke: the surface reported
	    # success for something the user never asked for, and any script gating on
	    # the exit code could not detect a typo or a renamed subcommand. Measured
	    # 2026-08-03 on 1.1.95: 21 of 28 probed tokens exited 0 this way, and every
	    # one of them was a single word with no whitespace. Real natural-language
	    # requests are phrases, so require one.
	    #
	    # Sub-command typos under a real command are already rejected correctly by
	    # that command's own parser (`auth statuss` -> exit 2). This makes the
	    # top-level dispatcher agree with the level below it.
	    if [[ $# -eq 0 && "$command" != *[[:space:]]* ]]; then
	      echo "$self: '$command' is not a hephaestus command." >&2
	      echo "See: $self --help  ·  to route it as a request: $self \"$command …\"" >&2
	      exit 2
	    fi
	    # A word this launcher does not own, but the sibling CLI does, is a
	    # wrong-tool mistake — not a request. The single-word guard above never
	    # sees it because these are typed with a subcommand (`graph list`),
	    # and the router then answered with Hub candidates at exit 0: a list of
	    # marketplace agents that reads exactly like the list of saved
	    # automations the user asked for (audit F9-4). Name the right tool.
	    case "$command" in
	      graph|automation|automations)
	        echo "$self: '$command' belongs to the agentlas CLI, not hephaestus." >&2
	        echo "Try: agentlas $command $*" >&2
	        exit 2
	        ;;
	    esac
	    # Whitespace-only input is not a request either.
	    if [[ -z "${command// /}" && $# -eq 0 ]]; then
	      echo "$self: no command given." >&2
	      usage >&2
	      exit 2
	    fi
	    run_python_module agentlas_cloud route "$command $*" --allow-local-routing
	    ;;
esac
