#!/usr/bin/env bash set -uo pipefail version="${HEPHAESTUS_REF:-v1.1.0}" repo="${HEPHAESTUS_REPO:-agentlas-ai/Hephaestus}" github_url="${HEPHAESTUS_GITHUB_URL:-https://github.com/$repo}" marketplace_name="${HEPHAESTUS_MARKETPLACE:-agentlas-core-engine}" plugin_name="${HEPHAESTUS_PLUGIN:-hephaestus}" old_plugin_name="${HEPHAESTUS_OLD_PLUGIN:-agentlas-meta-agent}" requested_source_dir="${HEPHAESTUS_SOURCE_DIR:-}" source_dir="$requested_source_dir" force="${HEPHAESTUS_FORCE:-1}" mcp_url="${AGENTLAS_MCP_URL:-https://agentlas.cloud/api/mcp/v1}" ok=0 failed=0 tmp_source_dir="" cleanup() { if [[ -n "$tmp_source_dir" ]]; then rm -rf "$tmp_source_dir" fi } trap cleanup EXIT log() { printf '%s\n' "$*" } warn() { printf 'WARN: %s\n' "$*" >&2 } have() { command -v "$1" >/dev/null 2>&1 } python_ok() { "$@" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)' >/dev/null 2>&1 } is_runtime_python_shim() { local candidate="$1" local resolved="" if [[ "$candidate" == */* ]]; then resolved="$candidate" else resolved="$(command -v "$candidate" 2>/dev/null || true)" fi [[ -n "$resolved" ]] || return 1 case "$resolved" in "$HOME/.agentlas/runtime/"*/bin/python3|"$HOME/.agentlas/runtime/current/bin/python3") return 0 ;; esac return 1 } python_candidate_ok() { local candidate="$1" if is_runtime_python_shim "$candidate"; then return 1 fi python_ok "$candidate" } resolve_python_cmd() { if [[ -n "${HEPHAESTUS_PYTHON:-}" ]] && python_candidate_ok "$HEPHAESTUS_PYTHON"; then printf '%s\n' "$HEPHAESTUS_PYTHON" return 0 fi local candidate for candidate in /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3; do if [[ -x "$candidate" ]] && python_candidate_ok "$candidate"; then printf '%s\n' "$candidate" return 0 fi done if have python3 && python_candidate_ok python3; then printf '%s\n' python3 return 0 fi if have python && python_candidate_ok python; then printf '%s\n' python return 0 fi if have py && 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 } run() { log "+ $*" "$@" } run_yes() { log "+ $*" printf 'y\n' | "$@" } try() { log "+ $*" "$@" } preflight_git() { if have git; then return 0 fi if [[ "$(uname -s)" == "Darwin" ]] && have xcode-select; then warn "git is missing. Starting Apple Command Line Tools installer." xcode-select --install >/dev/null 2>&1 || true fi warn "git is required for Claude/Codex/Gemini marketplace installs. Run git --version after Command Line Tools finishes, then rerun this installer." return 1 } ensure_downloaded_source() { if [[ -n "$source_dir" ]]; then return 0 fi if [[ -n "$tmp_source_dir" ]]; then source_dir="$tmp_source_dir/source" return 0 fi if ! have curl || ! have tar; then warn "curl and tar are required for Gemini extension install from a remote ref." return 1 fi tmp_source_dir="$(mktemp -d)" local archive="$tmp_source_dir/hephaestus.tar.gz" local archive_url="https://github.com/$repo/archive/$version.tar.gz" log "+ curl -fsSL $archive_url -o $archive" curl -fsSL "$archive_url" -o "$archive" || return 1 tar -xzf "$archive" -C "$tmp_source_dir" || return 1 local extracted extracted="$(find "$tmp_source_dir" -maxdepth 1 -type d -name 'Hephaestus-*' | head -n 1)" if [[ -z "$extracted" ]]; then warn "Downloaded Hephaestus source was not found in archive." return 1 fi mv "$extracted" "$tmp_source_dir/source" source_dir="$tmp_source_dir/source" } # Runtime-neutral install: every adapter (skills, commands, prompts, MCP) # resolves ~/.agentlas/runtime/current/bin/hephaestus FIRST, so harnesses # without a plugin cache (OpenCode, OpenClaw, Hermes, Cursor, Ollama-launched # local models) still find the runner. install_runtime_home() { ensure_downloaded_source || { warn "runtime home install skipped: no source."; return 1; } local plain="${version#v}" local home_dir="$HOME/.agentlas/runtime/$plain" log "== Hephaestus runtime home ==" rm -rf "$home_dir" mkdir -p "$home_dir" cp -R "$source_dir/bin" "$source_dir/agentlas_cloud" "$source_dir/ontology" "$home_dir/" || return 1 chmod +x "$home_dir/bin/hephaestus" \ "$home_dir/bin/hep-build" \ "$home_dir/bin/hep-network" \ "$home_dir/bin/hep-cloud" \ "$home_dir/bin/hep-search" \ "$home_dir/bin/hep-call" \ "$home_dir/bin/hep-upload" \ "$home_dir/bin/hep-storm" 2>/dev/null || true printf '%s\n' "$version" > "$home_dir/RELEASE" write_python3_shim "$home_dir/bin" || true if [[ ! -e "$home_dir/bin/Hephaestus" ]]; then ln -sfn hephaestus "$home_dir/bin/Hephaestus" 2>/dev/null || true fi rm -f "$home_dir/bin/Hephaestus-build" "$home_dir/bin/Hephaestus-search" \ "$home_dir/bin/Hephaestus-call" "$home_dir/bin/Hephaestus-storm" \ "$home_dir/bin/hephaestus-network" \ "$home_dir/bin/hephaestus-build" "$home_dir/bin/hephaests-network" \ "$home_dir/bin/hephaestus-search" "$home_dir/bin/hephaestus-call" \ "$home_dir/bin/hephaestus-storm" 2>/dev/null || true local current_link="$HOME/.agentlas/runtime/current" if [[ -e "$current_link" && ! -L "$current_link" ]]; then rm -rf "$current_link" fi ln -sfn "$home_dir" "$current_link" log "Installed runner: $HOME/.agentlas/runtime/current/bin/hephaestus" } write_python3_shim() { local bin_dir="$1" local py py="$(resolve_python_cmd || true)" rm -f "$bin_dir/python3" "$bin_dir/python3.cmd" 2>/dev/null || true [[ -n "$py" ]] || return 0 mkdir -p "$bin_dir" if [[ "$py" == "py -3" ]]; then cat > "$bin_dir/python3" <<'EOF' #!/usr/bin/env bash exec py -3 "$@" EOF printf '@py -3 %%*\r\n' > "$bin_dir/python3.cmd" else cat > "$bin_dir/python3" < "$bin_dir/python3.cmd" fi cat > "$bin_dir/hephaestus.cmd" <<'EOF' @echo off set "PYTHONUTF8=1" set "PYTHONIOENCODING=utf-8" set "PYTHONPATH=%~dp0..;%PYTHONPATH%" if exist "%~dp0python3.cmd" ( call "%~dp0python3.cmd" -m agentlas_cloud %* ) else ( py -3 -m agentlas_cloud %* || python -m agentlas_cloud %* ) EOF cat > "$bin_dir/hephaestus-env.cmd" <<'EOF' @echo off set "PYTHONUTF8=1" set "PYTHONIOENCODING=utf-8" set "PYTHONPATH=%~dp0..;%PYTHONPATH%" EOF chmod +x "$bin_dir/python3" } # AgentSkills-spec universal skill: ~/.agents/skills is read natively by # Codex (USER scope), OpenCode, OpenClaw, Cursor, and Crush. install_agents_skills() { ensure_downloaded_source || return 1 mkdir -p "$HOME/.agents/skills" local name src for name in hephaestus-network hephaestus-cloud; do src="$source_dir/skills/$name" [[ -d "$src" ]] || { warn "canonical $name skill not found."; return 1; } rm -rf "$HOME/.agents/skills/$name" cp -R "$src" "$HOME/.agents/skills/$name" done log "Installed universal skills: ~/.agents/skills/hephaestus-network and hephaestus-cloud" } remove_claude_existing() { try claude plugin uninstall "$plugin_name@$marketplace_name" >/dev/null 2>&1 || true try claude plugin uninstall "$old_plugin_name@$marketplace_name" >/dev/null 2>&1 || true try claude plugin marketplace remove "$marketplace_name" >/dev/null 2>&1 || true rm -rf "$HOME/.claude/plugins/cache/$marketplace_name/$plugin_name" 2>/dev/null || true rm -rf "$HOME/.claude/plugins/cache/$marketplace_name/$old_plugin_name" 2>/dev/null || true } install_claude() { if ! have claude; then warn "Claude CLI not found; skipped Claude plugin install." return 0 fi log "== Claude Code plugin ==" if [[ "$force" == "1" ]]; then remove_claude_existing else try claude plugin marketplace update "$marketplace_name" >/dev/null 2>&1 || true fi if [[ -n "$requested_source_dir" ]]; then run claude plugin marketplace add "$source_dir/claude" || return 1 else run claude plugin marketplace add "$github_url" --sparse .claude-plugin claude/plugins || return 1 fi run claude plugin install "$plugin_name@$marketplace_name" || return 1 try claude plugin enable "$plugin_name@$marketplace_name" >/dev/null 2>&1 || true write_claude_commands || warn "Claude global command refresh failed; copy .claude/commands/hep-*.md to ~/.claude/commands manually." log "agentlas Hub MCP is bundled with the plugin (.mcp.json) and registers automatically." ok=$((ok + 1)) } # Keep the user-global ~/.claude/commands copies in sync with this release. # Remove old entries first so stale symlinks from earlier installers do not # survive in the host app's command autocomplete cache. write_claude_commands() { ensure_downloaded_source || return 1 mkdir -p "$HOME/.claude/commands" local name src dest for name in hep-build.md hep-network.md hep-cloud.md hep-search.md hep-call.md hep-upload.md; do src="$source_dir/.claude/commands/$name" dest="$HOME/.claude/commands/$name" rm -f "$dest" cp "$src" "$dest" || return 1 done rm -f "$HOME/.claude/commands/hephaestus.md" "$HOME/.claude/commands/hephaests-network.md" \ "$HOME/.claude/commands/hephaestus-build.md" "$HOME/.claude/commands/hephaestus-network.md" \ "$HOME/.claude/commands/hephaestus-cloud.md" "$HOME/.claude/commands/hephaestus-search.md" \ "$HOME/.claude/commands/hephaestus-call.md" log "Refreshed Claude commands: /hep-build, /hep-network, /hep-cloud, /hep-search, /hep-call, /hep-upload" } remove_codex_existing() { try codex plugin remove "$plugin_name@$marketplace_name" >/dev/null 2>&1 || true try codex plugin remove "$old_plugin_name@$marketplace_name" >/dev/null 2>&1 || true try codex plugin marketplace remove "$marketplace_name" >/dev/null 2>&1 || true rm -rf "${CODEX_HOME:-$HOME/.codex}/plugins/cache/$marketplace_name/$plugin_name" 2>/dev/null || true } # Codex plugins cannot register slash commands (skills only), so the explicit # command surface is a custom prompt: ~/.codex/prompts/.md → # /prompts:. Top-level files only — Codex ignores subdirectories. write_codex_prompts() { ensure_downloaded_source || return 1 local prompts_src="$source_dir/codex/prompts" [[ -d "$prompts_src" ]] || { warn "codex prompts not found: $prompts_src"; return 1; } mkdir -p "$HOME/.codex/prompts" local name for name in hep-build.md hep-network.md hep-cloud.md hep-search.md hep-call.md hep-upload.md; do rm -f "$HOME/.codex/prompts/$name" cp "$prompts_src/$name" "$HOME/.codex/prompts/$name" || return 1 done rm -f "$HOME/.codex/prompts/hephaestus.md" "$HOME/.codex/prompts/hephaests-network.md" \ "$HOME/.codex/prompts/hephaestus-build.md" "$HOME/.codex/prompts/hephaestus-network.md" \ "$HOME/.codex/prompts/hephaestus-cloud.md" "$HOME/.codex/prompts/hephaestus-search.md" \ "$HOME/.codex/prompts/hephaestus-call.md" log "Installed Codex custom prompts: /prompts:hep-build, /prompts:hep-network, /prompts:hep-cloud, /prompts:hep-search, /prompts:hep-call, /prompts:hep-upload" } install_codex() { if ! have codex; then warn "Codex CLI not found; skipped Codex plugin install." return 0 fi log "== Codex plugin ==" if [[ "$force" == "1" ]]; then remove_codex_existing else try codex plugin marketplace upgrade "$marketplace_name" >/dev/null 2>&1 || true fi if [[ -n "$requested_source_dir" ]]; then run codex plugin marketplace add "$source_dir" || return 1 else run codex plugin marketplace add "$repo" --ref "$version" || return 1 fi run codex plugin add "$plugin_name@$marketplace_name" || return 1 write_codex_prompts || warn "Codex custom prompt install failed; copy codex/prompts/*.md to ~/.codex/prompts manually." register_codex_mcp || warn "Codex MCP registration failed; add it manually to ~/.codex/config.toml." ok=$((ok + 1)) } stamp_plugin_cache_releases() { local root dir count=0 for root in \ "$HOME/.claude/plugins/cache/$marketplace_name/$plugin_name" \ "${CODEX_HOME:-$HOME/.codex}/plugins/cache/$marketplace_name/$plugin_name" do [[ -d "$root" ]] || continue while IFS= read -r -d '' dir; do [[ -f "$dir/bin/hephaestus" ]] || continue printf '%s\n' "$version" > "$dir/RELEASE" || true write_python3_shim "$dir/bin" || true count=$((count + 1)) done < <(find "$root" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null) done if [[ "$count" -gt 0 ]]; then log "Stamped plugin cache release markers: $count" fi } # Codex 플러그인은 MCP 번들을 지원하지 않으므로 config.toml에 직접 등록한다. # rmcp 클라이언트 플래그가 없으면 HTTP MCP 자체가 붙지 않는다. register_codex_mcp() { local cfg="$HOME/.codex/config.toml" mkdir -p "$HOME/.codex" touch "$cfg" || return 1 if ! grep -q 'experimental_use_rmcp_client' "$cfg"; then if grep -q '^\[features\]' "$cfg"; then awk '{print} /^\[features\]/ && !done {print "experimental_use_rmcp_client = true"; done=1}' \ "$cfg" > "$cfg.tmp" && mv "$cfg.tmp" "$cfg" || return 1 else printf '\n[features]\nexperimental_use_rmcp_client = true\n' >> "$cfg" fi fi if ! grep -q '^\[mcp_servers\.agentlas\]' "$cfg"; then printf '\n[mcp_servers.agentlas]\nurl = "%s"\n' "$mcp_url" >> "$cfg" fi if ! grep -q '^\[mcp_servers\.hephaestus-network\]' "$cfg"; then printf '\n[mcp_servers.hephaestus-network]\ncommand = "%s"\nargs = ["mcp", "serve"]\n' \ "$HOME/.agentlas/runtime/current/bin/hephaestus" >> "$cfg" fi log "Registered agentlas Hub MCP + local hephaestus-network MCP in $cfg" } write_gemini_fallback_command() { local command_dir="$HOME/.gemini/commands" mkdir -p "$command_dir" local name for name in hep-build.toml hep-network.toml hep-cloud.toml hep-search.toml hep-call.toml hep-upload.toml; do rm -f "$command_dir/$name" cp "$source_dir/gemini/extension/commands/$name" "$command_dir/$name" || return 1 done rm -f "$command_dir/hephaestus.toml" "$command_dir/hephaests-network.toml" \ "$command_dir/hephaestus-build.toml" "$command_dir/hephaestus-network.toml" \ "$command_dir/hephaestus-cloud.toml" "$command_dir/hephaestus-search.toml" \ "$command_dir/hephaestus-call.toml" log "Installed Gemini fallback commands: /hep-build, /hep-network, /hep-cloud, /hep-search, /hep-call, /hep-upload" } install_gemini() { if ! have gemini; then warn "Gemini CLI not found; skipped Gemini extension install." return 0 fi log "== Gemini CLI extension and command ==" try gemini extensions uninstall hephaestus >/dev/null 2>&1 || true ensure_downloaded_source || return 1 local gemini_extension_dir="$source_dir/gemini/extension" if [[ ! -f "$gemini_extension_dir/gemini-extension.json" ]]; then warn "Gemini extension manifest not found: $gemini_extension_dir/gemini-extension.json" return 1 fi if [[ -z "${HEPHAESTUS_SOURCE_DIR:-}" ]]; then local stable_gemini_source="$HOME/.gemini/hephaestus-extension-source" rm -rf "$stable_gemini_source" mkdir -p "$stable_gemini_source" cp -R "$gemini_extension_dir"/. "$stable_gemini_source"/ gemini_extension_dir="$stable_gemini_source" fi run_yes gemini extensions install "$gemini_extension_dir" --consent --skip-settings || return 1 write_gemini_fallback_command || return 1 ok=$((ok + 1)) } antigravity_present() { [[ -d "$HOME/.gemini/antigravity" ]] && return 0 # "Antigravity IDE" 변형은 별도 데이터 디렉토리(~/.gemini/antigravity-ide)를 쓴다. [[ -d "$HOME/.gemini/antigravity-ide" ]] && return 0 [[ -n "${HEPHAESTUS_FORCE_ANTIGRAVITY:-}" ]] && return 0 ls -d /Applications/Antigravity*.app >/dev/null 2>&1 && return 0 return 1 } install_antigravity() { if ! antigravity_present; then warn "Antigravity not detected; skipped Antigravity workflow install." return 0 fi log "== Antigravity workflow ==" ensure_downloaded_source || return 1 # 두 데이터 디렉토리 변형 모두에 설치한다 — 어느 앱을 쓰든 새 3개 명령이 보이게. local installed=0 local data_dir for data_dir in "$HOME/.gemini/antigravity" "$HOME/.gemini/antigravity-ide"; do # 존재하는 데이터 디렉토리에만 설치하되, 둘 다 없으면 기본 경로를 생성한다. if [[ -d "$data_dir" || ( "$installed" -eq 0 && "$data_dir" == "$HOME/.gemini/antigravity" ) ]]; then local global_dir="$data_dir/global_workflows" mkdir -p "$global_dir" local name for name in hep-build.md hep-network.md hep-cloud.md hep-search.md hep-call.md hep-upload.md; do rm -f "$global_dir/$name" cp "$source_dir/antigravity/workflows/$name" "$global_dir/$name" || return 1 done rm -f "$global_dir/hephaestus.md" "$global_dir/hephaests-network.md" \ "$global_dir/hephaestus-build.md" "$global_dir/hephaestus-network.md" \ "$global_dir/hephaestus-cloud.md" "$global_dir/hephaestus-search.md" \ "$global_dir/hephaestus-call.md" log "Installed Antigravity global workflows: /hep-build, /hep-network, /hep-cloud, /hep-search, /hep-call, /hep-upload" installed=$((installed + 1)) fi done [[ "$installed" -gt 0 ]] || return 1 register_antigravity_mcp || warn "Antigravity MCP registration failed; add it manually to ~/.gemini/config/mcp_config.json." ok=$((ok + 1)) } # Antigravity는 ~/.gemini/config/mcp_config.json에서 MCP 서버를 읽는다 (serverUrl 키). register_antigravity_mcp() { local cfg_dir="$HOME/.gemini/config" local cfg="$cfg_dir/mcp_config.json" local py="" py="$(resolve_python_cmd || true)" if [[ -z "$py" ]]; then warn "python3 not found; add agentlas MCP to $cfg manually." return 0 fi mkdir -p "$cfg_dir" AGENTLAS_MCP_URL="$mcp_url" "$py" - "$cfg" <<'PY' || return 1 import json, os, sys path = sys.argv[1] url = os.environ["AGENTLAS_MCP_URL"] try: with open(path) as f: data = json.load(f) except (FileNotFoundError, ValueError): data = {} servers = data.setdefault("mcpServers", {}) servers.setdefault("agentlas", {"serverUrl": url}) with open(path, "w") as f: json.dump(data, f, indent=2) f.write("\n") PY log "Registered agentlas Hub MCP in $cfg" } # Cursor reads global commands (~/.cursor/commands/*.md) and skills # (~/.cursor/skills, plus ~/.agents/skills) in both the IDE and the CLI. install_cursor() { if [[ ! -d "$HOME/.cursor" ]] && ! have agent && ! have cursor-agent && ! have cursor; then warn "Cursor not detected; skipped Cursor command/skill install." return 0 fi log "== Cursor commands and skill ==" ensure_downloaded_source || return 1 mkdir -p "$HOME/.cursor/commands" "$HOME/.cursor/skills" local name for name in hep-build.md hep-network.md hep-cloud.md hep-search.md hep-call.md hep-upload.md; do rm -f "$HOME/.cursor/commands/$name" cp "$source_dir/cursor/plugin/commands/$name" "$HOME/.cursor/commands/$name" || return 1 done rm -f "$HOME/.cursor/commands/hephaestus.md" "$HOME/.cursor/commands/hephaests-network.md" \ "$HOME/.cursor/commands/hephaestus-build.md" "$HOME/.cursor/commands/hephaestus-network.md" \ "$HOME/.cursor/commands/hephaestus-cloud.md" "$HOME/.cursor/commands/hephaestus-search.md" \ "$HOME/.cursor/commands/hephaestus-call.md" for name in hephaestus-network hephaestus-cloud; do rm -rf "$HOME/.cursor/skills/$name" cp -R "$source_dir/skills/$name" "$HOME/.cursor/skills/$name" || return 1 done log "Installed Cursor commands (/hep-build, /hep-network, /hep-cloud, /hep-search, /hep-call, /hep-upload) and skills." ok=$((ok + 1)) } # OpenCode reads ~/.config/opencode/commands/*.md as /name slash commands and # ~/.agents/skills natively. install_opencode() { if ! have opencode && [[ ! -d "$HOME/.config/opencode" ]]; then warn "OpenCode not detected; skipped OpenCode command install." return 0 fi log "== OpenCode commands ==" ensure_downloaded_source || return 1 mkdir -p "$HOME/.config/opencode/commands" local name for name in hep-build.md hep-network.md hep-cloud.md hep-search.md hep-call.md hep-upload.md; do rm -f "$HOME/.config/opencode/commands/$name" cp "$source_dir/opencode/commands/$name" "$HOME/.config/opencode/commands/$name" || return 1 done rm -f "$HOME/.config/opencode/commands/hephaestus.md" "$HOME/.config/opencode/commands/hephaests-network.md" \ "$HOME/.config/opencode/commands/hephaestus-build.md" "$HOME/.config/opencode/commands/hephaestus-network.md" \ "$HOME/.config/opencode/commands/hephaestus-cloud.md" "$HOME/.config/opencode/commands/hephaestus-search.md" \ "$HOME/.config/opencode/commands/hephaestus-call.md" log "Installed OpenCode commands: /hep-build, /hep-network, /hep-cloud, /hep-search, /hep-call, /hep-upload" ok=$((ok + 1)) } # OpenClaw loads AgentSkills from ~/.openclaw/skills (and ~/.agents/skills); # user-invocable skills surface as slash commands via /skill. install_openclaw() { if ! have openclaw && [[ ! -d "$HOME/.openclaw" ]]; then warn "OpenClaw not detected; skipped OpenClaw skill install." return 0 fi log "== OpenClaw skill ==" ensure_downloaded_source || return 1 local name skill_src mkdir -p "$HOME/.openclaw/skills" for name in hephaestus-network hephaestus-cloud; do skill_src="$source_dir/openclaw/skills/$name" if have openclaw && openclaw skills install "$skill_src" --global >/dev/null 2>&1; then log "Installed OpenClaw skill via: openclaw skills install --global ($name)" else rm -rf "$HOME/.openclaw/skills/$name" cp -R "$skill_src" "$HOME/.openclaw/skills/$name" || return 1 fi done log "Installed OpenClaw skills: hephaestus-network and hephaestus-cloud" ok=$((ok + 1)) } # Hermes Agent (Nous Research) reads AgentSkills from ~/.hermes/skills. install_hermes() { if ! have hermes && [[ ! -d "$HOME/.hermes" ]]; then warn "Hermes Agent not detected; skipped Hermes skill install." return 0 fi log "== Hermes Agent skill ==" ensure_downloaded_source || return 1 mkdir -p "$HOME/.hermes/skills" local name for name in hephaestus-network hephaestus-cloud; do rm -rf "$HOME/.hermes/skills/$name" cp -R "$source_dir/skills/$name" "$HOME/.hermes/skills/$name" || return 1 done log "Installed Hermes skills: hephaestus-network and hephaestus-cloud (MCP: see hermes/README.md)" ok=$((ok + 1)) } # Hephaestus Network 2.0: create or migrate ~/.agentlas/networking on every # install/upgrade (idempotent; indexes only registered paths, never the home # folder). bootstrap_networking() { local py="" py="$(resolve_python_cmd || true)" if [[ -z "$py" ]]; then warn "python3 not found; skipped Hephaestus Network init. Install Python 3.9+ and run: hephaestus network init" return 0 fi if ! ensure_downloaded_source; then warn "Hephaestus Network init skipped: could not download the source archive (curl/tar). Run later: hephaestus network init" return 0 fi log "== Hephaestus Network (global routing structure) ==" local init_output if ! init_output="$(PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONPATH="$source_dir${PYTHONPATH:+:$PYTHONPATH}" $py -m agentlas_cloud network init 2>&1)"; then warn "Hephaestus Network init failed. Error was:" printf '%s\n' "$init_output" | tail -5 >&2 warn "Retry manually: PYTHONPATH= $py -m agentlas_cloud network init" return 1 fi PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONPATH="$source_dir${PYTHONPATH:+:$PYTHONPATH}" $py -m agentlas_cloud network reindex >/dev/null 2>&1 || true log "Initialized ~/.agentlas/networking (cards, policies, ledgers, local memory map)." } prune_legacy_public_surfaces() { local stale_md=( hephaestus.md hephaests-network.md agentlas-auto-activation.md agentlas-core-engine-meta-agent.md agentlas-packaging.md agentlas-security-scan.md clarify-question-loop.md mode-classification.md self-evolving-single-agent.md skill-lifecycle-promotion.md team-builder-packaging.md ) local name for name in "${stale_md[@]}"; do rm -f "$HOME/.claude/commands/$name" rm -f "$HOME/.codex/prompts/$name" rm -f "$HOME/.cursor/commands/$name" rm -f "$HOME/.config/opencode/commands/$name" rm -f "$HOME/.gemini/antigravity/global_workflows/$name" rm -f "$HOME/.gemini/antigravity-ide/global_workflows/$name" done rm -f "$HOME/.gemini/commands/hephaestus.toml" "$HOME/.gemini/commands/hephaests-network.toml" rm -f "$HOME/.gemini/commands/agentlas-auto-activation.toml" \ "$HOME/.gemini/commands/agentlas-core-engine-meta-agent.toml" \ "$HOME/.gemini/commands/agentlas-packaging.toml" \ "$HOME/.gemini/commands/agentlas-security-scan.toml" \ "$HOME/.gemini/commands/clarify-question-loop.toml" \ "$HOME/.gemini/commands/mode-classification.toml" \ "$HOME/.gemini/commands/self-evolving-single-agent.toml" \ "$HOME/.gemini/commands/skill-lifecycle-promotion.toml" \ "$HOME/.gemini/commands/team-builder-packaging.toml" find "$HOME/.claude/plugins/cache/$marketplace_name/$plugin_name" -maxdepth 1 -type d \ \( -name '0-7-4' -o -name '0.7.4' \) -exec rm -rf {} + 2>/dev/null || true find "${CODEX_HOME:-$HOME/.codex}/plugins/cache/$marketplace_name/$plugin_name" -maxdepth 1 -type d \ \( -name '0-7-4' -o -name '0.7.4' \) -exec rm -rf {} + 2>/dev/null || true log "Pruned legacy visible chat command files and stale 0.7.4 cache folders." } main() { log "Hephaestus one-touch install/update" log "repo: $repo" log "ref: $version" log "mode: force refresh=${force}" preflight_git || exit 1 install_runtime_home || { warn "Runtime home install failed."; failed=$((failed + 1)); } install_agents_skills || { warn "Universal ~/.agents/skills install failed."; failed=$((failed + 1)); } install_claude || { warn "Claude install failed."; failed=$((failed + 1)); } install_codex || { warn "Codex install failed."; failed=$((failed + 1)); } stamp_plugin_cache_releases || warn "Plugin cache release marker refresh failed." install_gemini || { warn "Gemini install failed."; failed=$((failed + 1)); } install_antigravity || { warn "Antigravity install failed."; failed=$((failed + 1)); } install_cursor || { warn "Cursor install failed."; failed=$((failed + 1)); } install_opencode || { warn "OpenCode install failed."; failed=$((failed + 1)); } install_openclaw || { warn "OpenClaw install failed."; failed=$((failed + 1)); } install_hermes || { warn "Hermes install failed."; failed=$((failed + 1)); } bootstrap_networking || warn "Hephaestus Network init failed; run 'hephaestus network init' manually." prune_legacy_public_surfaces log "" log "Installed/updated runtimes: $ok" log "Failed runtimes: $failed" log "" log "Public chat surface: six external commands are installed or refreshed; Agentlas native surfaces use plain language." log "Restart open Claude Code, Codex, Gemini, Antigravity, Cursor, OpenCode, OpenClaw, and Hermes apps." log "Then use:" log " Agentlas: describe the task in plain language; native tools choose the path" log " Claude Code: /reload-plugins, then /hep-build, /hep-network, /hep-cloud, /hep-search, /hep-call, /hep-upload" log " Codex: /prompts:hep-build, /prompts:hep-network, /prompts:hep-cloud, /prompts:hep-search, /prompts:hep-call, /prompts:hep-upload" log " Gemini CLI: /extensions list or /commands list, then /hep-build, /hep-network, /hep-cloud, /hep-search, /hep-call, /hep-upload" log " Antigravity: reopen the workspace, then /hep-build, /hep-network, /hep-cloud, /hep-search, /hep-call, /hep-upload" log " Cursor: /hep-build, /hep-network, /hep-cloud, /hep-search, /hep-call, /hep-upload" log " OpenCode: /hep-build, /hep-network, /hep-cloud, /hep-search, /hep-call, /hep-upload" log " OpenClaw: /skill hephaestus-network " log " Hermes: hephaestus-network skill (+ MCP, see hermes/README.md)" log " Shell/debug: hep-build \"\", hep-network \"\", hep-cloud \"\", hep-search \"\", hep-call \"agent-a,agent-b\" \"\", hep-upload , or hep-storm \"\" --background" log " Ollama/Gemma/DeepSeek local models: docs/local-models.md (MCP: hephaestus mcp serve)" log "" log "agentlas Hub MCP (agentlas.search, marketplace.*, agentlas.teams.*) was registered too." log "Try a plain-language prompt in any runtime, e.g.:" log " \"agentlas에서 ASO 도와주는 에이전트 찾아줘\" / \"find an agentlas agent for app store reviews\"" if [[ "$ok" -eq 0 || "$failed" -gt 0 ]]; then exit 1 fi } main "$@"