diff --git a/README_CN.md b/README_CN.md
new file mode 100644
index 000000000..9100635aa
--- /dev/null
+++ b/README_CN.md
@@ -0,0 +1,436 @@
+# Hermes Agent - 自进化 AI 代理
+
+
+
+
+
+## 简介
+
+**Hermes Agent** 是由 [Nous Research](https://nousresearch.com) 开发的自进化 AI 代理工具。它是唯一内置学习循环的代理系统——能够从经验中创建技能、在使用过程中改进技能、自主保留知识、搜索历史对话,并在多会话中建立对用户的深度理解模型。
+
+你可以在 $5 VPS、GPU 集群或几乎无空闲成本的无服务器基础设施上运行它。它不局限于你的笔记本电脑——你可以在云端 VM 上运行它,同时通过 Telegram 与它对话。
+
+## 核心特性
+
+### 🔄 闭环学习系统
+- **自主技能创建**:完成复杂任务后自动创建技能
+- **技能自改进**:在使用过程中持续优化技能
+- **智能记忆管理**:定期提示保存知识
+- **跨会话搜索**:FTS5 会话搜索 + LLM 摘要,实现跨会话回忆
+- **用户建模**:兼容 [Honcho](https://github.com/plastic-labs/honcho) 辩证用户建模
+- **开放标准**:支持 [agentskills.io](https://agentskills.io) 开放技能标准
+
+### 💬 多平台支持
+- **CLI 交互**:完整的 TUI(终端 UI),支持多行编辑、斜杠命令自动补全、对话历史、中断重定向、流式工具输出
+- **消息平台**:Telegram、Discord、Slack、WhatsApp、Signal、Email、Home Assistant 等——所有平台共享同一网关进程
+- **语音支持**:语音备忘录转录、跨平台对话连续性
+
+### ⏰ 定时自动化
+- **内置 Cron 调度器**:支持任何平台的任务投递
+- **自然语言任务**:每日报告、夜间备份、每周审计——全部用自然语言配置,无人值守运行
+
+### 🤖 代理委派与并行
+- **子代理委派**:生成独立的子代理处理并行工作流
+- **RPC 脚本调用**:编写通过 RPC 调用工具的 Python 脚本,将多步骤流水线压缩为零上下文成本的轮次
+
+### 🌍 无处不在的运行环境
+- **六种终端后端**:本地、Docker、SSH、Daytona、Singularity、Modal
+- **无服务器持久化**:Daytona 和 Modal 提供无服务器持久化——代理环境在空闲时休眠,按需唤醒,会话间几乎无成本
+- **灵活部署**:在 $5 VPS 或 GPU 集群上运行
+
+### 🧪 研究就绪
+- **批量轨迹生成**
+- **Atropos RL 环境**
+- **轨迹压缩**:用于训练下一代工具调用模型
+
+### 🔌 模型无关
+支持任何你想用的模型:
+- [Nous Portal](https://portal.nousresearch.com)
+- [OpenRouter](https://openrouter.ai)(200+ 模型)
+- [NVIDIA NIM](https://build.nvidia.com)(Nemotron)
+- [小米 MiMo](https://platform.xiaomimimo.com)
+- [z.ai/GLM](https://z.ai)
+- [Kimi/Moonshot](https://platform.moonshot.ai)
+- [MiniMax](https://www.minimax.io)
+- [Hugging Face](https://huggingface.co)
+- OpenAI、Anthropic
+- 或你自己的端点
+
+使用 `hermes model` 切换模型——无需代码更改,无锁定。
+
+---
+
+## 安装指南
+
+### 方式一:标准安装(网络畅通)
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
+```
+
+支持 Linux、macOS、WSL2 和 Android(Termux)。
+
+### 方式二:本地安装(适合中国用户或离线环境)
+
+如果你在中国或网络环境无法访问 GitHub,可以使用以下方法:
+
+**步骤 1:下载代码**
+- 从 GitHub 下载 ZIP 或使用镜像站下载
+- 或使用 Git 镜像克隆
+
+**步骤 2:使用本地安装模式**
+
+```bash
+# 进入下载的代码目录
+cd /path/to/hermes-agent
+
+# 使用 --local 模式安装,指定已下载的目录
+bash install.sh --local --dir /path/to/hermes-agent
+```
+
+**安装选项说明:**
+
+| 选项 | 说明 |
+|------|------|
+| `--local` | 使用本地目录,跳过 git clone |
+| `--dir PATH` | 指定安装目录路径 |
+| `--no-venv` | 不创建虚拟环境 |
+| `--skip-setup` | 跳过交互式设置向导 |
+| `--hermes-home PATH` | 指定数据目录(默认:~/.hermes) |
+
+**完整示例:**
+
+```bash
+# 假设你已将代码下载到 /home/user/hermes-agent
+cd /home/user/hermes-agent
+
+# 使用本地目录安装
+bash install.sh --local --dir /home/user/hermes-agent
+
+# 或者跳过设置向导(适合自动化部署)
+bash install.sh --local --dir /home/user/hermes-agent --skip-setup
+```
+
+### 方式三:开发者安装(已克隆仓库)
+
+如果你是开发者,已经克隆了仓库:
+
+```bash
+cd hermes-agent
+./setup-hermes.sh
+```
+
+### Windows 用户
+
+原生 Windows 不支持。请安装 [WSL2](https://learn.microsoft.com/zh-cn/windows/wsl/install) 然后使用上述方式。
+
+---
+
+## 快速入门
+
+### 安装后步骤
+
+```bash
+# 重新加载 shell 配置
+source ~/.bashrc # 或 source ~/.zshrc
+
+# 开始对话
+hermes
+```
+
+### 常用命令
+
+| 命令 | 说明 |
+|------|------|
+| `hermes` | 启动交互式 CLI,开始对话 |
+| `hermes model` | 选择 LLM 提供商和模型 |
+| `hermes tools` | 配置启用哪些工具 |
+| `hermes config set` | 设置单个配置值 |
+| `hermes gateway` | 启动消息网关(Telegram、Discord 等) |
+| `hermes setup` | 运行完整设置向导(一次配置所有) |
+| `hermes update` | 更新到最新版本 |
+| `hermes doctor` | 诊断任何问题 |
+
+### 斜杠命令速查
+
+无论是 CLI 还是消息平台,以下斜杠命令都可用:
+
+| 命令 | 说明 |
+|------|------|
+| `/new` 或 `/reset` | 开始新对话 |
+| `/model [provider:model]` | 切换模型 |
+| `/personality [name]` | 设置个性 |
+| `/retry`, `/undo` | 重试或撤销上一轮 |
+| `/compress`, `/usage`, `/insights` | 压缩上下文、查看使用情况、洞察 |
+| `/skills` 或 `/` | 浏览技能 |
+| `/stop` | 中断当前工作(消息平台) |
+| `/platforms` | 平台特定状态(CLI) |
+| `/status`, `/sethome` | 状态、设置主目录(消息平台) |
+
+---
+
+## 项目架构
+
+### 项目结构
+
+```
+hermes-agent/
+├── run_agent.py # AIAgent 类 — 核心对话循环
+├── model_tools.py # 工具编排,discover_builtin_tools(), handle_function_call()
+├── toolsets.py # 工具集定义,_HERMES_CORE_TOOLS 列表
+├── cli.py # HermesCLI 类 — 交互式 CLI 编排器
+├── hermes_state.py # SessionDB — SQLite 会话存储(FTS5 搜索)
+├── agent/ # 代理内部模块
+│ ├── prompt_builder.py # 系统提示词构建
+│ ├── context_compressor.py # 自动上下文压缩
+│ ├── prompt_caching.py # Anthropic 提示词缓存
+│ ├── auxiliary_client.py # 辅助 LLM 客户端(视觉、摘要)
+│ ├── model_metadata.py # 模型上下文长度、token 估算
+│ ├── display.py # KawaiiSpinner、工具预览格式化
+│ └── skill_commands.py # 技能斜杠命令(CLI/网关共享)
+├── hermes_cli/ # CLI 子命令和设置
+│ ├── main.py # 入口点 — 所有 `hermes` 子命令
+│ ├── config.py # DEFAULT_CONFIG、OPTIONAL_ENV_VARS、迁移
+│ ├── commands.py # 斜杠命令定义 + SlashCommandCompleter
+│ ├── setup.py # 交互式设置向导
+│ ├── skin_engine.py # 皮肤/主题引擎 — CLI 视觉定制
+│ ├── skills_config.py # `hermes skills` — 按平台启用/禁用技能
+│ ├── tools_config.py # `hermes tools` — 按平台启用/禁用工具
+│ └── auth.py # 提供商凭证解析
+├── tools/ # 工具实现(每个工具一个文件)
+│ ├── registry.py # 中央工具注册表(schema、handler、dispatch)
+│ ├── terminal_tool.py # 终端编排
+│ ├── file_tools.py # 文件读写/搜索/补丁
+│ ├── web_tools.py # Web 搜索/提取(Parallel + Firecrawl)
+│ ├── browser_tool.py # 浏览器自动化
+│ ├── mcp_tool.py # MCP 客户端(约 1050 行)
+│ └── environments/ # 终端后端(local、docker、ssh、modal、daytona、singularity)
+├── gateway/ # 消息平台网关
+│ ├── run.py # 主循环、斜杠命令、消息分发
+│ ├── session.py # SessionStore — 对话持久化
+│ └── platforms/ # 适配器:telegram、discord、slack、whatsapp、homeassistant、signal、qqbot
+├── ui-tui/ # Ink(React)终端 UI — `hermes --tui`
+│ ├── src/entry.tsx # TTY 入口 + render()
+│ ├── src/app.tsx # 主状态机和 UI
+│ └── src/components/ # Ink 组件(品牌、Markdown、提示、选择器等)
+├── tui_gateway/ # TUI 的 Python JSON-RPC 后端
+│ ├── entry.py # stdio 入口点
+│ ├── server.py # RPC 处理器和会话逻辑
+│ └── slash_worker.py # 斜杠命令的持久 HermesCLI 子进程
+├── acp_adapter/ # ACP 服务器(VS Code / Zed / JetBrains 集成)
+├── cron/ # 调度器(jobs.py、scheduler.py)
+├── tests/ # Pytest 测试套件(约 3000 个测试)
+└── batch_runner.py # 并行批量处理
+```
+
+### 核心架构
+
+#### 文件依赖链
+
+```
+tools/registry.py (无依赖 — 被所有工具文件导入)
+ ↑
+tools/*.py (每个在导入时调用 registry.register())
+ ↑
+model_tools.py (导入 tools/registry + 触发工具发现)
+ ↑
+run_agent.py, cli.py, batch_runner.py, environments/
+```
+
+#### AIAgent 核心循环
+
+核心循环在 `run_conversation()` 中,完全同步:
+
+```python
+while api_call_count < self.max_iterations and self.iteration_budget.remaining > 0:
+ response = client.chat.completions.create(model=model, messages=messages, tools=tool_schemas)
+ if response.tool_calls:
+ for tool_call in response.tool_calls:
+ result = handle_function_call(tool_call.name, tool_call.args, task_id)
+ messages.append(tool_result_message(result))
+ api_call_count += 1
+ else:
+ return response.content
+```
+
+消息遵循 OpenAI 格式:`{"role": "system/user/assistant/tool", ...}`。推理内容存储在 `assistant_msg["reasoning"]` 中。
+
+#### CLI 架构
+
+- **Rich** 用于横幅/面板,**prompt_toolkit** 用于带自动补全的输入
+- **KawaiiSpinner** (`agent/display.py`) — API 调用期间的动画表情,工具结果的 `┊` 活动馈送
+- `load_cli_config()` 合并硬编码默认值 + 用户配置 YAML
+- **皮肤引擎** (`hermes_cli/skin_engine.py`) — 数据驱动的 CLI 主题化
+- `process_command()` 根据从中央注册表 `resolve_command()` 解析的规范名称调度
+
+---
+
+## 学习路径
+
+### 新手入门
+
+1. **安装并运行**
+ - 按照安装指南完成安装
+ - 运行 `hermes setup` 配置 API 密钥
+ - 输入 `hermes` 开始第一次对话
+
+2. **理解基本概念**
+ - 阅读 `AGENTS.md` 了解项目结构
+ - 查看 `run_agent.py` 理解核心循环
+ - 了解工具系统如何工作
+
+3. **尝试常用功能**
+ - 使用文件工具读取和编辑文件
+ - 尝试终端工具执行命令
+ - 体验技能系统
+
+### 进阶学习
+
+1. **工具开发**
+ - 学习 `tools/registry.py` 的注册机制
+ - 参考现有工具(如 `tools/file_tools.py`)
+ - 理解工具集(toolsets)的概念
+
+2. **技能系统**
+ - 了解技能如何存储在 `~/.hermes/skills/`
+ - 学习如何创建和使用技能
+ - 理解技能与工具的区别
+
+3. **消息网关**
+ - 配置 Telegram/Discord 机器人
+ - 理解网关架构
+ - 学习平台适配器开发
+
+### 高级开发
+
+1. **核心修改**
+ - 修改 `run_agent.py` 的对话循环
+ - 扩展 `model_tools.py` 的工具编排
+ - 自定义提示词构建器
+
+2. **插件开发**
+ - 学习内存插件系统(`plugins/memory/`)
+ - 理解 MCP 集成
+ - 开发自定义工具集
+
+3. **研究与训练**
+ - 使用 `environments/` 进行 RL 训练
+ - 轨迹压缩与模型训练
+ - 批量轨迹生成
+
+---
+
+## 配置说明
+
+### 配置文件位置
+
+| 文件 | 路径 | 说明 |
+|------|------|------|
+| 主配置 | `~/.hermes/config.yaml` | 所有设置 |
+| API 密钥 | `~/.hermes/.env` | 环境变量和密钥 |
+| 个性定义 | `~/.hermes/SOUL.md` | 代理个性和语气 |
+| 技能目录 | `~/.hermes/skills/` | 用户技能存储 |
+
+### 常用配置项
+
+```yaml
+# ~/.hermes/config.yaml 示例
+
+model:
+ provider: openrouter
+ name: anthropic/claude-opus-4.6
+
+display:
+ skin: default # default, ares, mono, slate
+ tool_progress: true
+
+tools:
+ enabled:
+ - terminal
+ - file
+ - web
+```
+
+### 环境变量
+
+在 `~/.hermes/.env` 中配置:
+
+```bash
+# LLM 提供商
+OPENROUTER_API_KEY=your-key
+ANTHROPIC_API_KEY=your-key
+OPENAI_API_KEY=your-key
+
+# 消息平台
+TELEGRAM_BOT_TOKEN=your-token
+DISCORD_BOT_TOKEN=your-token
+SLACK_BOT_TOKEN=your-token
+
+# 其他
+NOUS_PORTAL_API_KEY=your-key
+```
+
+---
+
+## 常见问题
+
+### Q: 在中国无法访问 GitHub 怎么办?
+
+**A:** 使用 `--local` 安装模式:
+
+1. 通过镜像站或其他方式下载代码
+2. 运行:`bash install.sh --local --dir /path/to/downloaded/hermes-agent`
+
+### Q: 支持哪些模型?
+
+**A:** 支持任何兼容 OpenAI API 的模型提供商,包括:
+- OpenRouter(200+ 模型)
+- Anthropic
+- OpenAI
+- Nous Portal
+- 国内模型(Kimi、智谱、MiniMax 等,通过 OpenRouter 或自定义端点)
+
+### Q: 如何切换模型?
+
+**A:** 使用:
+```bash
+hermes model # 交互式选择
+hermes model openrouter/anthropic/claude-opus-4.6 # 直接指定
+```
+
+### Q: 如何在服务器上后台运行?
+
+**A:** 使用网关服务:
+```bash
+hermes gateway install # 安装为 systemd 服务
+hermes gateway start # 启动服务
+```
+
+或手动后台运行:
+```bash
+nohup hermes gateway > ~/.hermes/logs/gateway.log 2>&1 &
+```
+
+### Q: 技能和工具有什么区别?
+
+**A:**
+- **工具 (Tools)**: 由代码实现的底层能力(文件操作、终端、Web 搜索等),40+ 内置工具
+- **技能 (Skills)**: 由代理从经验中学习或用户创建的高层流程,使用 Markdown 格式定义,可共享和改进
+
+---
+
+## 相关链接
+
+- **官方文档**: https://hermes-agent.nousresearch.com/docs/
+- **Discord 社区**: https://discord.gg/NousResearch
+- **技能中心**: https://agentskills.io
+- **GitHub**: https://github.com/NousResearch/hermes-agent
+- **问题反馈**: https://github.com/NousResearch/hermes-agent/issues
+
+---
+
+## 许可证
+
+MIT 许可证 — 详见 [LICENSE](LICENSE)。
+
+由 [Nous Research](https://nousresearch.com) 开发。
diff --git a/install.sh b/install.sh
new file mode 100644
index 000000000..7ce69f4cd
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,1481 @@
+#!/bin/bash
+# ============================================================================
+# Hermes Agent Installer
+# ============================================================================
+# Installation script for Linux, macOS, and Android/Termux.
+# Uses uv for desktop/server installs and Python's stdlib venv + pip on Termux.
+#
+# Usage:
+# curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
+#
+# Or with options:
+# curl -fsSL ... | bash -s -- --no-venv --skip-setup
+#
+# ============================================================================
+
+set -e
+
+# Colors
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[0;33m'
+BLUE='\033[0;34m'
+MAGENTA='\033[0;35m'
+CYAN='\033[0;36m'
+NC='\033[0m' # No Color
+BOLD='\033[1m'
+
+# Configuration
+REPO_URL_SSH="git@github.com:NousResearch/hermes-agent.git"
+REPO_URL_HTTPS="https://github.com/NousResearch/hermes-agent.git"
+HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
+INSTALL_DIR="${HERMES_INSTALL_DIR:-$HERMES_HOME/hermes-agent}"
+PYTHON_VERSION="3.11"
+NODE_VERSION="22"
+
+# Options
+USE_VENV=true
+RUN_SETUP=true
+BRANCH="main"
+USE_LOCAL_DIR=false
+
+# Detect non-interactive mode (e.g. curl | bash)
+# When stdin is not a terminal, read -p will fail with EOF,
+# causing set -e to silently abort the entire script.
+if [ -t 0 ]; then
+ IS_INTERACTIVE=true
+else
+ IS_INTERACTIVE=false
+fi
+
+# Parse arguments
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --no-venv)
+ USE_VENV=false
+ shift
+ ;;
+ --skip-setup)
+ RUN_SETUP=false
+ shift
+ ;;
+ --branch)
+ BRANCH="$2"
+ shift 2
+ ;;
+ --dir)
+ INSTALL_DIR="$2"
+ shift 2
+ ;;
+ --local)
+ USE_LOCAL_DIR=true
+ shift
+ ;;
+ --hermes-home)
+ HERMES_HOME="$2"
+ shift 2
+ ;;
+ -h|--help)
+ echo "Hermes Agent Installer"
+ echo ""
+ echo "Usage: install.sh [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " --no-venv Don't create virtual environment"
+ echo " --skip-setup Skip interactive setup wizard"
+ echo " --branch NAME Git branch to install (default: main)"
+ echo " --dir PATH Installation directory (default: ~/.hermes/hermes-agent)"
+ echo " --local Use existing local directory (--dir), skip git clone (for offline/China users)"
+ echo " --hermes-home PATH Data directory (default: ~/.hermes, or \$HERMES_HOME)"
+ echo " -h, --help Show this help"
+ exit 0
+ ;;
+ *)
+ echo "Unknown option: $1"
+ exit 1
+ ;;
+ esac
+done
+
+# ============================================================================
+# Helper functions
+# ============================================================================
+
+print_banner() {
+ echo ""
+ echo -e "${MAGENTA}${BOLD}"
+ echo "����������������������������������������������������������������������������������������������������������������������"
+ echo "�� ? Hermes Agent Installer ��"
+ echo "����������������������������������������������������������������������������������������������������������������������"
+ echo "�� An open source AI agent by Nous Research. ��"
+ echo "����������������������������������������������������������������������������������������������������������������������"
+ echo -e "${NC}"
+}
+
+log_info() {
+ echo -e "${CYAN}��${NC} $1"
+}
+
+log_success() {
+ echo -e "${GREEN}?${NC} $1"
+}
+
+log_warn() {
+ echo -e "${YELLOW}?${NC} $1"
+}
+
+log_error() {
+ echo -e "${RED}?${NC} $1"
+}
+
+prompt_yes_no() {
+ local question="$1"
+ local default="${2:-yes}"
+ local prompt_suffix
+ local answer=""
+
+ # Use case patterns (not ${var,,}) so this works on bash 3.2 (macOS /bin/bash).
+ case "$default" in
+ [yY]|[yY][eE][sS]|[tT][rR][uU][eE]|1) prompt_suffix="[Y/n]" ;;
+ *) prompt_suffix="[y/N]" ;;
+ esac
+
+ if [ "$IS_INTERACTIVE" = true ]; then
+ read -r -p "$question $prompt_suffix " answer || answer=""
+ elif [ -r /dev/tty ] && [ -w /dev/tty ]; then
+ printf "%s %s " "$question" "$prompt_suffix" > /dev/tty
+ IFS= read -r answer < /dev/tty || answer=""
+ else
+ answer=""
+ fi
+
+ answer="${answer#"${answer%%[![:space:]]*}"}"
+ answer="${answer%"${answer##*[![:space:]]}"}"
+
+ if [ -z "$answer" ]; then
+ case "$default" in
+ [yY]|[yY][eE][sS]|[tT][rR][uU][eE]|1) return 0 ;;
+ *) return 1 ;;
+ esac
+ fi
+
+ case "$answer" in
+ [yY]|[yY][eE][sS]) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+is_termux() {
+ [ -n "${TERMUX_VERSION:-}" ] || [[ "${PREFIX:-}" == *"com.termux/files/usr"* ]]
+}
+
+get_command_link_dir() {
+ if is_termux && [ -n "${PREFIX:-}" ]; then
+ echo "$PREFIX/bin"
+ else
+ echo "$HOME/.local/bin"
+ fi
+}
+
+get_command_link_display_dir() {
+ if is_termux && [ -n "${PREFIX:-}" ]; then
+ echo '$PREFIX/bin'
+ else
+ echo '~/.local/bin'
+ fi
+}
+
+get_hermes_command_path() {
+ local link_dir
+ link_dir="$(get_command_link_dir)"
+ if [ -x "$link_dir/hermes" ]; then
+ echo "$link_dir/hermes"
+ else
+ echo "hermes"
+ fi
+}
+
+# ============================================================================
+# System detection
+# ============================================================================
+
+detect_os() {
+ case "$(uname -s)" in
+ Linux*)
+ if is_termux; then
+ OS="android"
+ DISTRO="termux"
+ else
+ OS="linux"
+ if [ -f /etc/os-release ]; then
+ . /etc/os-release
+ DISTRO="$ID"
+ else
+ DISTRO="unknown"
+ fi
+ fi
+ ;;
+ Darwin*)
+ OS="macos"
+ DISTRO="macos"
+ ;;
+ CYGWIN*|MINGW*|MSYS*)
+ OS="windows"
+ DISTRO="windows"
+ log_error "Windows detected. Please use the PowerShell installer:"
+ log_info " irm https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1 | iex"
+ exit 1
+ ;;
+ *)
+ OS="unknown"
+ DISTRO="unknown"
+ log_warn "Unknown operating system"
+ ;;
+ esac
+
+ log_success "Detected: $OS ($DISTRO)"
+}
+
+# ============================================================================
+# Dependency checks
+# ============================================================================
+
+install_uv() {
+ if [ "$DISTRO" = "termux" ]; then
+ log_info "Termux detected �� using Python's stdlib venv + pip instead of uv"
+ UV_CMD=""
+ return 0
+ fi
+
+ log_info "Checking for uv package manager..."
+
+ # Check common locations for uv
+ if command -v uv &> /dev/null; then
+ UV_CMD="uv"
+ UV_VERSION=$($UV_CMD --version 2>/dev/null)
+ log_success "uv found ($UV_VERSION)"
+ return 0
+ fi
+
+ # Check ~/.local/bin (default uv install location) even if not on PATH yet
+ if [ -x "$HOME/.local/bin/uv" ]; then
+ UV_CMD="$HOME/.local/bin/uv"
+ UV_VERSION=$($UV_CMD --version 2>/dev/null)
+ log_success "uv found at ~/.local/bin ($UV_VERSION)"
+ return 0
+ fi
+
+ # Check ~/.cargo/bin (alternative uv install location)
+ if [ -x "$HOME/.cargo/bin/uv" ]; then
+ UV_CMD="$HOME/.cargo/bin/uv"
+ UV_VERSION=$($UV_CMD --version 2>/dev/null)
+ log_success "uv found at ~/.cargo/bin ($UV_VERSION)"
+ return 0
+ fi
+
+ # Install uv
+ log_info "Installing uv (fast Python package manager)..."
+ if curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null; then
+ # uv installs to ~/.local/bin by default
+ if [ -x "$HOME/.local/bin/uv" ]; then
+ UV_CMD="$HOME/.local/bin/uv"
+ elif [ -x "$HOME/.cargo/bin/uv" ]; then
+ UV_CMD="$HOME/.cargo/bin/uv"
+ elif command -v uv &> /dev/null; then
+ UV_CMD="uv"
+ else
+ log_error "uv installed but not found on PATH"
+ log_info "Try adding ~/.local/bin to your PATH and re-running"
+ exit 1
+ fi
+ UV_VERSION=$($UV_CMD --version 2>/dev/null)
+ log_success "uv installed ($UV_VERSION)"
+ else
+ log_error "Failed to install uv"
+ log_info "Install manually: https://docs.astral.sh/uv/getting-started/installation/"
+ exit 1
+ fi
+}
+
+check_python() {
+ if [ "$DISTRO" = "termux" ]; then
+ log_info "Checking Termux Python..."
+ if command -v python >/dev/null 2>&1; then
+ PYTHON_PATH="$(command -v python)"
+ if "$PYTHON_PATH" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' 2>/dev/null; then
+ PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null)
+ log_success "Python found: $PYTHON_FOUND_VERSION"
+ return 0
+ fi
+ fi
+
+ log_info "Installing Python via pkg..."
+ pkg install -y python >/dev/null
+ PYTHON_PATH="$(command -v python)"
+ PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null)
+ log_success "Python installed: $PYTHON_FOUND_VERSION"
+ return 0
+ fi
+
+ log_info "Checking Python $PYTHON_VERSION..."
+
+ # Let uv handle Python �� it can download and manage Python versions
+ # First check if a suitable Python is already available
+ if $UV_CMD python find "$PYTHON_VERSION" &> /dev/null; then
+ PYTHON_PATH=$($UV_CMD python find "$PYTHON_VERSION")
+ PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null)
+ log_success "Python found: $PYTHON_FOUND_VERSION"
+ return 0
+ fi
+
+ # Python not found �� use uv to install it (no sudo needed!)
+ log_info "Python $PYTHON_VERSION not found, installing via uv..."
+ if $UV_CMD python install "$PYTHON_VERSION"; then
+ PYTHON_PATH=$($UV_CMD python find "$PYTHON_VERSION")
+ PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null)
+ log_success "Python installed: $PYTHON_FOUND_VERSION"
+ else
+ log_error "Failed to install Python $PYTHON_VERSION"
+ log_info "Install Python $PYTHON_VERSION manually, then re-run this script"
+ exit 1
+ fi
+}
+
+check_git() {
+ log_info "Checking Git..."
+
+ if command -v git &> /dev/null; then
+ GIT_VERSION=$(git --version | awk '{print $3}')
+ log_success "Git $GIT_VERSION found"
+ return 0
+ fi
+
+ log_error "Git not found"
+
+ if [ "$DISTRO" = "termux" ]; then
+ log_info "Installing Git via pkg..."
+ pkg install -y git >/dev/null
+ if command -v git >/dev/null 2>&1; then
+ GIT_VERSION=$(git --version | awk '{print $3}')
+ log_success "Git $GIT_VERSION installed"
+ return 0
+ fi
+ fi
+
+ log_info "Please install Git:"
+
+ case "$OS" in
+ linux)
+ case "$DISTRO" in
+ ubuntu|debian)
+ log_info " sudo apt update && sudo apt install git"
+ ;;
+ fedora)
+ log_info " sudo dnf install git"
+ ;;
+ arch)
+ log_info " sudo pacman -S git"
+ ;;
+ *)
+ log_info " Use your package manager to install git"
+ ;;
+ esac
+ ;;
+ android)
+ log_info " pkg install git"
+ ;;
+ macos)
+ log_info " xcode-select --install"
+ log_info " Or: brew install git"
+ ;;
+ esac
+
+ exit 1
+}
+
+check_node() {
+ log_info "Checking Node.js (for browser tools)..."
+
+ if command -v node &> /dev/null; then
+ local found_ver=$(node --version)
+ log_success "Node.js $found_ver found"
+ HAS_NODE=true
+ return 0
+ fi
+
+ # Check our own managed install from a previous run
+ if [ -x "$HERMES_HOME/node/bin/node" ]; then
+ export PATH="$HERMES_HOME/node/bin:$PATH"
+ local found_ver=$("$HERMES_HOME/node/bin/node" --version)
+ log_success "Node.js $found_ver found (Hermes-managed)"
+ HAS_NODE=true
+ return 0
+ fi
+
+ if [ "$DISTRO" = "termux" ]; then
+ log_info "Node.js not found �� installing Node.js via pkg..."
+ else
+ log_info "Node.js not found �� installing Node.js $NODE_VERSION LTS..."
+ fi
+ install_node
+}
+
+install_node() {
+ if [ "$DISTRO" = "termux" ]; then
+ log_info "Installing Node.js via pkg..."
+ if pkg install -y nodejs >/dev/null; then
+ local installed_ver
+ installed_ver=$(node --version 2>/dev/null)
+ log_success "Node.js $installed_ver installed via pkg"
+ HAS_NODE=true
+ else
+ log_warn "Failed to install Node.js via pkg"
+ HAS_NODE=false
+ fi
+ return 0
+ fi
+
+ local arch=$(uname -m)
+ local node_arch
+ case "$arch" in
+ x86_64) node_arch="x64" ;;
+ aarch64|arm64) node_arch="arm64" ;;
+ armv7l) node_arch="armv7l" ;;
+ *)
+ log_warn "Unsupported architecture ($arch) for Node.js auto-install"
+ log_info "Install manually: https://nodejs.org/en/download/"
+ HAS_NODE=false
+ return 0
+ ;;
+ esac
+
+ local node_os
+ case "$OS" in
+ linux) node_os="linux" ;;
+ macos) node_os="darwin" ;;
+ *)
+ log_warn "Unsupported OS for Node.js auto-install"
+ HAS_NODE=false
+ return 0
+ ;;
+ esac
+
+ # Resolve the latest v22.x.x tarball name from the index page
+ local index_url="https://nodejs.org/dist/latest-v${NODE_VERSION}.x/"
+ local tarball_name
+ tarball_name=$(curl -fsSL "$index_url" \
+ | grep -oE "node-v${NODE_VERSION}\.[0-9]+\.[0-9]+-${node_os}-${node_arch}\.tar\.xz" \
+ | head -1)
+
+ # Fallback to .tar.gz if .tar.xz not available
+ if [ -z "$tarball_name" ]; then
+ tarball_name=$(curl -fsSL "$index_url" \
+ | grep -oE "node-v${NODE_VERSION}\.[0-9]+\.[0-9]+-${node_os}-${node_arch}\.tar\.gz" \
+ | head -1)
+ fi
+
+ if [ -z "$tarball_name" ]; then
+ log_warn "Could not find Node.js $NODE_VERSION binary for $node_os-$node_arch"
+ log_info "Install manually: https://nodejs.org/en/download/"
+ HAS_NODE=false
+ return 0
+ fi
+
+ local download_url="${index_url}${tarball_name}"
+ local tmp_dir
+ tmp_dir=$(mktemp -d)
+
+ log_info "Downloading $tarball_name..."
+ if ! curl -fsSL "$download_url" -o "$tmp_dir/$tarball_name"; then
+ log_warn "Download failed"
+ rm -rf "$tmp_dir"
+ HAS_NODE=false
+ return 0
+ fi
+
+ log_info "Extracting to ~/.hermes/node/..."
+ if [[ "$tarball_name" == *.tar.xz ]]; then
+ tar xf "$tmp_dir/$tarball_name" -C "$tmp_dir"
+ else
+ tar xzf "$tmp_dir/$tarball_name" -C "$tmp_dir"
+ fi
+
+ local extracted_dir
+ extracted_dir=$(ls -d "$tmp_dir"/node-v* 2>/dev/null | head -1)
+
+ if [ ! -d "$extracted_dir" ]; then
+ log_warn "Extraction failed"
+ rm -rf "$tmp_dir"
+ HAS_NODE=false
+ return 0
+ fi
+
+ # Place into ~/.hermes/node/ and symlink binaries to ~/.local/bin/
+ rm -rf "$HERMES_HOME/node"
+ mkdir -p "$HERMES_HOME"
+ mv "$extracted_dir" "$HERMES_HOME/node"
+ rm -rf "$tmp_dir"
+
+ mkdir -p "$HOME/.local/bin"
+ ln -sf "$HERMES_HOME/node/bin/node" "$HOME/.local/bin/node"
+ ln -sf "$HERMES_HOME/node/bin/npm" "$HOME/.local/bin/npm"
+ ln -sf "$HERMES_HOME/node/bin/npx" "$HOME/.local/bin/npx"
+
+ export PATH="$HERMES_HOME/node/bin:$PATH"
+
+ local installed_ver
+ installed_ver=$("$HERMES_HOME/node/bin/node" --version 2>/dev/null)
+ log_success "Node.js $installed_ver installed to ~/.hermes/node/"
+ HAS_NODE=true
+}
+
+install_system_packages() {
+ # Detect what's missing
+ HAS_RIPGREP=false
+ HAS_FFMPEG=false
+ local need_ripgrep=false
+ local need_ffmpeg=false
+
+ log_info "Checking ripgrep (fast file search)..."
+ if command -v rg &> /dev/null; then
+ log_success "$(rg --version | head -1) found"
+ HAS_RIPGREP=true
+ else
+ need_ripgrep=true
+ fi
+
+ log_info "Checking ffmpeg (TTS voice messages)..."
+ if command -v ffmpeg &> /dev/null; then
+ local ffmpeg_ver=$(ffmpeg -version 2>/dev/null | head -1 | awk '{print $3}')
+ log_success "ffmpeg $ffmpeg_ver found"
+ HAS_FFMPEG=true
+ else
+ need_ffmpeg=true
+ fi
+
+ # Termux always needs the Android build toolchain for the tested pip path,
+ # even when ripgrep/ffmpeg are already present.
+ if [ "$DISTRO" = "termux" ]; then
+ local termux_pkgs=(clang rust make pkg-config libffi openssl)
+ if [ "$need_ripgrep" = true ]; then
+ termux_pkgs+=("ripgrep")
+ fi
+ if [ "$need_ffmpeg" = true ]; then
+ termux_pkgs+=("ffmpeg")
+ fi
+
+ log_info "Installing Termux packages: ${termux_pkgs[*]}"
+ if pkg install -y "${termux_pkgs[@]}" >/dev/null; then
+ [ "$need_ripgrep" = true ] && HAS_RIPGREP=true && log_success "ripgrep installed"
+ [ "$need_ffmpeg" = true ] && HAS_FFMPEG=true && log_success "ffmpeg installed"
+ log_success "Termux build dependencies installed"
+ return 0
+ fi
+
+ log_warn "Could not auto-install all Termux packages"
+ log_info "Install manually: pkg install ${termux_pkgs[*]}"
+ return 0
+ fi
+
+ # Nothing to install �� done
+ if [ "$need_ripgrep" = false ] && [ "$need_ffmpeg" = false ]; then
+ return 0
+ fi
+
+ # Build a human-readable description + package list
+ local desc_parts=()
+ local pkgs=()
+ if [ "$need_ripgrep" = true ]; then
+ desc_parts+=("ripgrep for faster file search")
+ pkgs+=("ripgrep")
+ fi
+ if [ "$need_ffmpeg" = true ]; then
+ desc_parts+=("ffmpeg for TTS voice messages")
+ pkgs+=("ffmpeg")
+ fi
+ local description
+ description=$(IFS=" and "; echo "${desc_parts[*]}")
+
+ # ���� macOS: brew ����
+ if [ "$OS" = "macos" ]; then
+ if command -v brew &> /dev/null; then
+ log_info "Installing ${pkgs[*]} via Homebrew..."
+ if brew install "${pkgs[@]}"; then
+ [ "$need_ripgrep" = true ] && HAS_RIPGREP=true && log_success "ripgrep installed"
+ [ "$need_ffmpeg" = true ] && HAS_FFMPEG=true && log_success "ffmpeg installed"
+ return 0
+ fi
+ fi
+ log_warn "Could not auto-install (brew not found or install failed)"
+ log_info "Install manually: brew install ${pkgs[*]}"
+ return 0
+ fi
+
+ # ���� Linux: resolve package manager command ����
+ local pkg_install=""
+ case "$DISTRO" in
+ ubuntu|debian) pkg_install="apt install -y" ;;
+ fedora) pkg_install="dnf install -y" ;;
+ arch) pkg_install="pacman -S --noconfirm" ;;
+ esac
+
+ if [ -n "$pkg_install" ]; then
+ local install_cmd="$pkg_install ${pkgs[*]}"
+
+ # Prevent needrestart/whiptail dialogs from blocking non-interactive installs
+ case "$DISTRO" in
+ ubuntu|debian) export DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a ;;
+ esac
+
+ # Already root �� just install
+ if [ "$(id -u)" -eq 0 ]; then
+ log_info "Installing ${pkgs[*]}..."
+ if $install_cmd; then
+ [ "$need_ripgrep" = true ] && HAS_RIPGREP=true && log_success "ripgrep installed"
+ [ "$need_ffmpeg" = true ] && HAS_FFMPEG=true && log_success "ffmpeg installed"
+ return 0
+ fi
+ # Passwordless sudo �� just install
+ elif command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then
+ log_info "Installing ${pkgs[*]}..."
+ if sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a $install_cmd; then
+ [ "$need_ripgrep" = true ] && HAS_RIPGREP=true && log_success "ripgrep installed"
+ [ "$need_ffmpeg" = true ] && HAS_FFMPEG=true && log_success "ffmpeg installed"
+ return 0
+ fi
+ # sudo needs password �� ask once for everything
+ elif command -v sudo &> /dev/null; then
+ if [ "$IS_INTERACTIVE" = true ]; then
+ echo ""
+ log_info "sudo is needed ONLY to install optional system packages (${pkgs[*]}) via your package manager."
+ log_info "Hermes Agent itself does not require or retain root access."
+ if prompt_yes_no "Install ${description}? (requires sudo)" "no"; then
+ if sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a $install_cmd; then
+ [ "$need_ripgrep" = true ] && HAS_RIPGREP=true && log_success "ripgrep installed"
+ [ "$need_ffmpeg" = true ] && HAS_FFMPEG=true && log_success "ffmpeg installed"
+ return 0
+ fi
+ fi
+ elif [ -e /dev/tty ]; then
+ # Non-interactive (e.g. curl | bash) but a terminal is available.
+ # Read the prompt from /dev/tty (same approach the setup wizard uses).
+ echo ""
+ log_info "sudo is needed ONLY to install optional system packages (${pkgs[*]}) via your package manager."
+ log_info "Hermes Agent itself does not require or retain root access."
+ if prompt_yes_no "Install ${description}?" "yes"; then
+ if sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a $install_cmd < /dev/tty; then
+ [ "$need_ripgrep" = true ] && HAS_RIPGREP=true && log_success "ripgrep installed"
+ [ "$need_ffmpeg" = true ] && HAS_FFMPEG=true && log_success "ffmpeg installed"
+ return 0
+ fi
+ fi
+ else
+ log_warn "Non-interactive mode and no terminal available �� cannot install system packages"
+ log_info "Install manually after setup completes: sudo $install_cmd"
+ fi
+ fi
+ fi
+
+ # ���� Fallback for ripgrep: cargo ����
+ if [ "$need_ripgrep" = true ] && [ "$HAS_RIPGREP" = false ]; then
+ if command -v cargo &> /dev/null; then
+ log_info "Trying cargo install ripgrep (no sudo needed)..."
+ if cargo install ripgrep; then
+ log_success "ripgrep installed via cargo"
+ HAS_RIPGREP=true
+ fi
+ fi
+ fi
+
+ # ���� Show manual instructions for anything still missing ����
+ if [ "$HAS_RIPGREP" = false ] && [ "$need_ripgrep" = true ]; then
+ log_warn "ripgrep not installed (file search will use grep fallback)"
+ show_manual_install_hint "ripgrep"
+ fi
+ if [ "$HAS_FFMPEG" = false ] && [ "$need_ffmpeg" = true ]; then
+ log_warn "ffmpeg not installed (TTS voice messages will be limited)"
+ show_manual_install_hint "ffmpeg"
+ fi
+}
+
+show_manual_install_hint() {
+ local pkg="$1"
+ log_info "To install $pkg manually:"
+ case "$OS" in
+ linux)
+ case "$DISTRO" in
+ ubuntu|debian) log_info " sudo apt install $pkg" ;;
+ fedora) log_info " sudo dnf install $pkg" ;;
+ arch) log_info " sudo pacman -S $pkg" ;;
+ *) log_info " Use your package manager or visit the project homepage" ;;
+ esac
+ ;;
+ android)
+ log_info " pkg install $pkg"
+ ;;
+ macos) log_info " brew install $pkg" ;;
+ esac
+}
+
+# ============================================================================
+# Installation
+# ============================================================================
+
+clone_repo() {
+ log_info "Installing to $INSTALL_DIR..."
+
+ if [ "$USE_LOCAL_DIR" = true ]; then
+ log_info "Using --local mode: skipping git clone, using existing directory..."
+
+ if [ ! -d "$INSTALL_DIR" ]; then
+ log_error "Directory not found: $INSTALL_DIR"
+ log_info "Please specify an existing directory with --dir PATH"
+ log_info "Or download the repository first and point --dir to it"
+ exit 1
+ fi
+
+ if [ ! -f "$INSTALL_DIR/pyproject.toml" ]; then
+ log_error "Directory does not appear to be a hermes-agent repository"
+ log_info "Expected to find pyproject.toml in: $INSTALL_DIR"
+ log_info "Please verify the directory contains the hermes-agent source code"
+ exit 1
+ fi
+
+ cd "$INSTALL_DIR"
+ log_success "Using local directory: $INSTALL_DIR"
+ return 0
+ fi
+
+ if [ -d "$INSTALL_DIR" ]; then
+ if [ -d "$INSTALL_DIR/.git" ]; then
+ log_info "Existing installation found, updating..."
+ cd "$INSTALL_DIR"
+
+ local autostash_ref=""
+ if [ -n "$(git status --porcelain)" ]; then
+ local stash_name
+ stash_name="hermes-install-autostash-$(date -u +%Y%m%d-%H%M%S)"
+ log_info "Local changes detected, stashing before update..."
+ git stash push --include-untracked -m "$stash_name"
+ autostash_ref="$(git rev-parse --verify refs/stash)"
+ fi
+
+ git fetch origin
+ git checkout "$BRANCH"
+ git pull --ff-only origin "$BRANCH"
+
+ if [ -n "$autostash_ref" ]; then
+ local restore_now="yes"
+ if [ -t 0 ] && [ -t 1 ]; then
+ echo
+ log_warn "Local changes were stashed before updating."
+ log_warn "Restoring them may reapply local customizations onto the updated codebase."
+ printf "Restore local changes now? [Y/n] "
+ read -r restore_answer
+ case "$restore_answer" in
+ ""|y|Y|yes|YES|Yes) restore_now="yes" ;;
+ *) restore_now="no" ;;
+ esac
+ fi
+
+ if [ "$restore_now" = "yes" ]; then
+ log_info "Restoring local changes..."
+ if git stash apply "$autostash_ref"; then
+ git stash drop "$autostash_ref" >/dev/null
+ log_warn "Local changes were restored on top of the updated codebase."
+ log_warn "Review git diff / git status if Hermes behaves unexpectedly."
+ else
+ log_error "Update succeeded, but restoring local changes failed. Your changes are still preserved in git stash."
+ log_info "Resolve manually with: git stash apply $autostash_ref"
+ exit 1
+ fi
+ else
+ log_info "Skipped restoring local changes."
+ log_info "Your changes are still preserved in git stash."
+ log_info "Restore manually with: git stash apply $autostash_ref"
+ fi
+ fi
+ else
+ log_error "Directory exists but is not a git repository: $INSTALL_DIR"
+ log_info "To use an existing non-git directory, add --local flag"
+ log_info "Example: bash install.sh --local --dir $INSTALL_DIR"
+ exit 1
+ fi
+ else
+ # Try SSH first (for private repo access), fall back to HTTPS
+ # GIT_SSH_COMMAND disables interactive prompts and sets a short timeout
+ # so SSH fails fast instead of hanging when no key is configured.
+ log_info "Trying SSH clone..."
+ if GIT_SSH_COMMAND="ssh -o BatchMode=yes -o ConnectTimeout=5" \
+ git clone --branch "$BRANCH" "$REPO_URL_SSH" "$INSTALL_DIR" 2>/dev/null; then
+ log_success "Cloned via SSH"
+ else
+ rm -rf "$INSTALL_DIR" 2>/dev/null # Clean up partial SSH clone
+ log_info "SSH failed, trying HTTPS..."
+ if git clone --branch "$BRANCH" "$REPO_URL_HTTPS" "$INSTALL_DIR"; then
+ log_success "Cloned via HTTPS"
+ else
+ log_error "Failed to clone repository"
+ log_info "For users in China or with network issues:"
+ log_info " 1. Download the repository manually (e.g., from Gitee or GitHub mirror)"
+ log_info " 2. Run: bash install.sh --local --dir /path/to/downloaded/hermes-agent"
+ exit 1
+ fi
+ fi
+ fi
+
+ cd "$INSTALL_DIR"
+
+ log_success "Repository ready"
+}
+
+setup_venv() {
+ if [ "$USE_VENV" = false ]; then
+ log_info "Skipping virtual environment (--no-venv)"
+ return 0
+ fi
+
+ if [ "$DISTRO" = "termux" ]; then
+ log_info "Creating virtual environment with Termux Python..."
+
+ if [ -d "venv" ]; then
+ log_info "Virtual environment already exists, recreating..."
+ rm -rf venv
+ fi
+
+ "$PYTHON_PATH" -m venv venv
+ log_success "Virtual environment ready ($(./venv/bin/python --version 2>/dev/null))"
+ return 0
+ fi
+
+ log_info "Creating virtual environment with Python $PYTHON_VERSION..."
+
+ if [ -d "venv" ]; then
+ log_info "Virtual environment already exists, recreating..."
+ rm -rf venv
+ fi
+
+ # uv creates the venv and pins the Python version in one step
+ $UV_CMD venv venv --python "$PYTHON_VERSION"
+
+ log_success "Virtual environment ready (Python $PYTHON_VERSION)"
+}
+
+install_deps() {
+ log_info "Installing dependencies..."
+
+ if [ "$DISTRO" = "termux" ]; then
+ if [ "$USE_VENV" = true ]; then
+ export VIRTUAL_ENV="$INSTALL_DIR/venv"
+ PIP_PYTHON="$INSTALL_DIR/venv/bin/python"
+ else
+ PIP_PYTHON="$PYTHON_PATH"
+ fi
+
+ if [ -z "${ANDROID_API_LEVEL:-}" ]; then
+ ANDROID_API_LEVEL="$(getprop ro.build.version.sdk 2>/dev/null || true)"
+ if [ -z "$ANDROID_API_LEVEL" ]; then
+ ANDROID_API_LEVEL=24
+ fi
+ export ANDROID_API_LEVEL
+ log_info "Using ANDROID_API_LEVEL=$ANDROID_API_LEVEL for Android wheel builds"
+ fi
+
+ "$PIP_PYTHON" -m pip install --upgrade pip setuptools wheel >/dev/null
+ if ! "$PIP_PYTHON" -m pip install -e '.[termux]' -c constraints-termux.txt; then
+ log_warn "Termux feature install (.[termux]) failed, trying base install..."
+ if ! "$PIP_PYTHON" -m pip install -e '.' -c constraints-termux.txt; then
+ log_error "Package installation failed on Termux."
+ log_info "Ensure these packages are installed: pkg install clang rust make pkg-config libffi openssl"
+ log_info "Then re-run: cd $INSTALL_DIR && python -m pip install -e '.[termux]' -c constraints-termux.txt"
+ exit 1
+ fi
+ fi
+
+ log_success "Main package installed"
+ log_info "Termux note: browser/WhatsApp tooling is not installed by default; see the Termux guide for optional follow-up steps."
+
+ if [ -d "tinker-atropos" ] && [ -f "tinker-atropos/pyproject.toml" ]; then
+ log_info "tinker-atropos submodule found �� skipping install (optional, for RL training)"
+ log_info " To install later: $PIP_PYTHON -m pip install -e \"./tinker-atropos\""
+ fi
+
+ log_success "All dependencies installed"
+ return 0
+ fi
+
+ if [ "$USE_VENV" = true ]; then
+ # Tell uv to install into our venv (no need to activate)
+ export VIRTUAL_ENV="$INSTALL_DIR/venv"
+ fi
+
+ # On Debian/Ubuntu (including WSL), some Python packages need build tools.
+ # Check and offer to install them if missing.
+ if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; then
+ local need_build_tools=false
+ for pkg in gcc python3-dev libffi-dev; do
+ if ! dpkg -s "$pkg" &>/dev/null; then
+ need_build_tools=true
+ break
+ fi
+ done
+ if [ "$need_build_tools" = true ]; then
+ log_info "Some build tools may be needed for Python packages..."
+ if command -v sudo &> /dev/null; then
+ if sudo -n true 2>/dev/null; then
+ sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y -qq build-essential python3-dev libffi-dev >/dev/null 2>&1 || true
+ log_success "Build tools installed"
+ else
+ log_info "sudo is needed ONLY to install build tools (build-essential, python3-dev, libffi-dev) via apt."
+ log_info "Hermes Agent itself does not require or retain root access."
+ if prompt_yes_no "Install build tools?" "yes"; then
+ sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y -qq build-essential python3-dev libffi-dev >/dev/null 2>&1 || true
+ log_success "Build tools installed"
+ fi
+ fi
+ fi
+ fi
+ fi
+
+ # Install the main package in editable mode with all extras.
+ # Try [all] first, fall back to base install if extras have issues.
+ ALL_INSTALL_LOG=$(mktemp)
+ if ! $UV_CMD pip install -e ".[all]" 2>"$ALL_INSTALL_LOG"; then
+ log_warn "Full install (.[all]) failed, trying base install..."
+ log_info "Reason: $(tail -5 "$ALL_INSTALL_LOG" | head -3)"
+ rm -f "$ALL_INSTALL_LOG"
+ if ! $UV_CMD pip install -e "."; then
+ log_error "Package installation failed."
+ log_info "Check that build tools are installed: sudo apt install build-essential python3-dev"
+ log_info "Then re-run: cd $INSTALL_DIR && uv pip install -e '.[all]'"
+ exit 1
+ fi
+ else
+ rm -f "$ALL_INSTALL_LOG"
+ fi
+
+ log_success "Main package installed"
+
+ # tinker-atropos (RL training) is optional �� skip by default.
+ # To enable RL tools: git submodule update --init tinker-atropos && uv pip install -e "./tinker-atropos"
+ if [ -d "tinker-atropos" ] && [ -f "tinker-atropos/pyproject.toml" ]; then
+ log_info "tinker-atropos submodule found �� skipping install (optional, for RL training)"
+ log_info " To install: $UV_CMD pip install -e \"./tinker-atropos\""
+ fi
+
+ log_success "All dependencies installed"
+}
+
+setup_path() {
+ log_info "Setting up hermes command..."
+
+ if [ "$USE_VENV" = true ]; then
+ HERMES_BIN="$INSTALL_DIR/venv/bin/hermes"
+ else
+ HERMES_BIN="$(which hermes 2>/dev/null || echo "")"
+ if [ -z "$HERMES_BIN" ]; then
+ log_warn "hermes not found on PATH after install"
+ return 0
+ fi
+ fi
+
+ # Verify the entry point script was actually generated
+ if [ ! -x "$HERMES_BIN" ]; then
+ log_warn "hermes entry point not found at $HERMES_BIN"
+ log_info "This usually means the pip install didn't complete successfully."
+ if [ "$DISTRO" = "termux" ]; then
+ log_info "Try: cd $INSTALL_DIR && python -m pip install -e '.[termux]' -c constraints-termux.txt"
+ else
+ log_info "Try: cd $INSTALL_DIR && uv pip install -e '.[all]'"
+ fi
+ return 0
+ fi
+
+ local command_link_dir
+ local command_link_display_dir
+ command_link_dir="$(get_command_link_dir)"
+ command_link_display_dir="$(get_command_link_display_dir)"
+
+ # Create a user-facing shim for the hermes command.
+ mkdir -p "$command_link_dir"
+ ln -sf "$HERMES_BIN" "$command_link_dir/hermes"
+ log_success "Symlinked hermes �� $command_link_display_dir/hermes"
+
+ if [ "$DISTRO" = "termux" ]; then
+ export PATH="$command_link_dir:$PATH"
+ log_info "$command_link_display_dir is the native Termux command path"
+ log_success "hermes command ready"
+ return 0
+ fi
+
+ # Check if ~/.local/bin is on PATH; if not, add it to shell config.
+ # Detect the user's actual login shell (not the shell running this script,
+ # which is always bash when piped from curl).
+ if ! echo "$PATH" | tr ':' '\n' | grep -q "^$command_link_dir$"; then
+ SHELL_CONFIGS=()
+ IS_FISH=false
+ LOGIN_SHELL="$(basename "${SHELL:-/bin/bash}")"
+ case "$LOGIN_SHELL" in
+ zsh)
+ [ -f "$HOME/.zshrc" ] && SHELL_CONFIGS+=("$HOME/.zshrc")
+ [ -f "$HOME/.zprofile" ] && SHELL_CONFIGS+=("$HOME/.zprofile")
+ # If neither exists, create ~/.zshrc (common on fresh macOS installs)
+ if [ ${#SHELL_CONFIGS[@]} -eq 0 ]; then
+ touch "$HOME/.zshrc"
+ SHELL_CONFIGS+=("$HOME/.zshrc")
+ fi
+ ;;
+ bash)
+ [ -f "$HOME/.bashrc" ] && SHELL_CONFIGS+=("$HOME/.bashrc")
+ [ -f "$HOME/.bash_profile" ] && SHELL_CONFIGS+=("$HOME/.bash_profile")
+ ;;
+ fish)
+ # fish uses ~/.config/fish/config.fish and fish_add_path �� not export PATH=
+ IS_FISH=true
+ FISH_CONFIG="$HOME/.config/fish/config.fish"
+ mkdir -p "$(dirname "$FISH_CONFIG")"
+ touch "$FISH_CONFIG"
+ ;;
+ *)
+ [ -f "$HOME/.bashrc" ] && SHELL_CONFIGS+=("$HOME/.bashrc")
+ [ -f "$HOME/.zshrc" ] && SHELL_CONFIGS+=("$HOME/.zshrc")
+ ;;
+ esac
+ # Also ensure ~/.profile has it (sourced by login shells on
+ # Ubuntu/Debian/WSL even when ~/.bashrc is skipped)
+ [ "$IS_FISH" = "false" ] && [ -f "$HOME/.profile" ] && SHELL_CONFIGS+=("$HOME/.profile")
+
+ PATH_LINE='export PATH="$HOME/.local/bin:$PATH"'
+
+ for SHELL_CONFIG in "${SHELL_CONFIGS[@]}"; do
+ if ! grep -v '^[[:space:]]*#' "$SHELL_CONFIG" 2>/dev/null | grep -qE 'PATH=.*\.local/bin'; then
+ echo "" >> "$SHELL_CONFIG"
+ echo "# Hermes Agent �� ensure ~/.local/bin is on PATH" >> "$SHELL_CONFIG"
+ echo "$PATH_LINE" >> "$SHELL_CONFIG"
+ log_success "Added ~/.local/bin to PATH in $SHELL_CONFIG"
+ fi
+ done
+
+ # fish uses fish_add_path instead of export PATH=...
+ if [ "$IS_FISH" = "true" ]; then
+ if ! grep -q 'fish_add_path.*\.local/bin' "$FISH_CONFIG" 2>/dev/null; then
+ echo "" >> "$FISH_CONFIG"
+ echo "# Hermes Agent �� ensure ~/.local/bin is on PATH" >> "$FISH_CONFIG"
+ echo 'fish_add_path "$HOME/.local/bin"' >> "$FISH_CONFIG"
+ log_success "Added ~/.local/bin to PATH in $FISH_CONFIG"
+ fi
+ fi
+
+ if [ "$IS_FISH" = "false" ] && [ ${#SHELL_CONFIGS[@]} -eq 0 ]; then
+ log_warn "Could not detect shell config file to add ~/.local/bin to PATH"
+ log_info "Add manually: $PATH_LINE"
+ fi
+ else
+ log_info "~/.local/bin already on PATH"
+ fi
+
+ # Export for current session so hermes works immediately
+ export PATH="$command_link_dir:$PATH"
+
+ log_success "hermes command ready"
+}
+
+copy_config_templates() {
+ log_info "Setting up configuration files..."
+
+ # Create ~/.hermes directory structure (config at top level, code in subdir)
+ mkdir -p "$HERMES_HOME"/{cron,sessions,logs,pairing,hooks,image_cache,audio_cache,memories,skills,whatsapp/session}
+
+ # Create .env at ~/.hermes/.env (top level, easy to find)
+ if [ ! -f "$HERMES_HOME/.env" ]; then
+ if [ -f "$INSTALL_DIR/.env.example" ]; then
+ cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env"
+ log_success "Created ~/.hermes/.env from template"
+ else
+ touch "$HERMES_HOME/.env"
+ log_success "Created ~/.hermes/.env"
+ fi
+ else
+ log_info "~/.hermes/.env already exists, keeping it"
+ fi
+
+ # Create config.yaml at ~/.hermes/config.yaml (top level, easy to find)
+ if [ ! -f "$HERMES_HOME/config.yaml" ]; then
+ if [ -f "$INSTALL_DIR/cli-config.yaml.example" ]; then
+ cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml"
+ log_success "Created ~/.hermes/config.yaml from template"
+ fi
+ else
+ log_info "~/.hermes/config.yaml already exists, keeping it"
+ fi
+
+ # Create SOUL.md if it doesn't exist (global persona file)
+ if [ ! -f "$HERMES_HOME/SOUL.md" ]; then
+ cat > "$HERMES_HOME/SOUL.md" << 'SOUL_EOF'
+# Hermes Agent Persona
+
+
+SOUL_EOF
+ log_success "Created ~/.hermes/SOUL.md (edit to customize personality)"
+ fi
+
+ log_success "Configuration directory ready: ~/.hermes/"
+
+ # Seed bundled skills into ~/.hermes/skills/ (manifest-based, one-time per skill)
+ log_info "Syncing bundled skills to ~/.hermes/skills/ ..."
+ if "$INSTALL_DIR/venv/bin/python" "$INSTALL_DIR/tools/skills_sync.py" 2>/dev/null; then
+ log_success "Skills synced to ~/.hermes/skills/"
+ else
+ # Fallback: simple directory copy if Python sync fails
+ if [ -d "$INSTALL_DIR/skills" ] && [ ! "$(ls -A "$HERMES_HOME/skills/" 2>/dev/null | grep -v '.bundled_manifest')" ]; then
+ cp -r "$INSTALL_DIR/skills/"* "$HERMES_HOME/skills/" 2>/dev/null || true
+ log_success "Skills copied to ~/.hermes/skills/"
+ fi
+ fi
+}
+
+install_node_deps() {
+ if [ "$HAS_NODE" = false ]; then
+ log_info "Skipping Node.js dependencies (Node not installed)"
+ return 0
+ fi
+
+ if [ "$DISTRO" = "termux" ]; then
+ log_info "Skipping automatic Node/browser dependency setup on Termux"
+ log_info "Browser automation and WhatsApp bridge are not part of the tested Termux install path yet."
+ log_info "If you want to experiment manually later, run: cd $INSTALL_DIR && npm install"
+ return 0
+ fi
+
+ if [ -f "$INSTALL_DIR/package.json" ]; then
+ log_info "Installing Node.js dependencies (browser tools)..."
+ cd "$INSTALL_DIR"
+ npm install --silent 2>/dev/null || {
+ log_warn "npm install failed (browser tools may not work)"
+ }
+ log_success "Node.js dependencies installed"
+
+ # Install Playwright browser + system dependencies.
+ # Playwright's --with-deps only supports apt-based systems natively.
+ # For Arch/Manjaro we install the system libs via pacman first.
+ # Other systems must install Chromium dependencies manually.
+ log_info "Installing browser engine (Playwright Chromium)..."
+ case "$DISTRO" in
+ ubuntu|debian|raspbian|pop|linuxmint|elementary|zorin|kali|parrot)
+ log_info "Playwright may request sudo to install browser system dependencies (shared libraries)."
+ log_info "This is standard Playwright setup �� Hermes itself does not require root access."
+ cd "$INSTALL_DIR" && npx playwright install --with-deps chromium 2>/dev/null || {
+ log_warn "Playwright browser installation failed �� browser tools will not work."
+ log_warn "Try running manually: cd $INSTALL_DIR && npx playwright install --with-deps chromium"
+ }
+ ;;
+ arch|manjaro)
+ if command -v pacman &> /dev/null; then
+ log_info "Arch/Manjaro detected �� installing Chromium system dependencies via pacman..."
+ if command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then
+ sudo NEEDRESTART_MODE=a pacman -S --noconfirm --needed \
+ nss atk at-spi2-core cups libdrm libxkbcommon mesa pango cairo alsa-lib >/dev/null 2>&1 || true
+ elif [ "$(id -u)" -eq 0 ]; then
+ pacman -S --noconfirm --needed \
+ nss atk at-spi2-core cups libdrm libxkbcommon mesa pango cairo alsa-lib >/dev/null 2>&1 || true
+ else
+ log_warn "Cannot install browser deps without sudo. Run manually:"
+ log_warn " sudo pacman -S nss atk at-spi2-core cups libdrm libxkbcommon mesa pango cairo alsa-lib"
+ fi
+ fi
+ cd "$INSTALL_DIR" && npx playwright install chromium 2>/dev/null || {
+ log_warn "Playwright browser installation failed �� browser tools will not work."
+ }
+ ;;
+ fedora|rhel|centos|rocky|alma)
+ log_warn "Playwright does not support automatic dependency installation on RPM-based systems."
+ log_info "Install Chromium system dependencies manually before using browser tools:"
+ log_info " sudo dnf install nss atk at-spi2-core cups-libs libdrm libxkbcommon mesa-libgbm pango cairo alsa-lib"
+ cd "$INSTALL_DIR" && npx playwright install chromium 2>/dev/null || {
+ log_warn "Playwright browser installation failed �� install dependencies above and retry."
+ }
+ ;;
+ opensuse*|sles)
+ log_warn "Playwright does not support automatic dependency installation on zypper-based systems."
+ log_info "Install Chromium system dependencies manually before using browser tools:"
+ log_info " sudo zypper install mozilla-nss libatk-1_0-0 at-spi2-core cups-libs libdrm2 libxkbcommon0 Mesa-libgbm1 pango cairo libasound2"
+ cd "$INSTALL_DIR" && npx playwright install chromium 2>/dev/null || {
+ log_warn "Playwright browser installation failed �� install dependencies above and retry."
+ }
+ ;;
+ *)
+ log_warn "Playwright does not support automatic dependency installation on $DISTRO."
+ log_info "Install Chromium/browser system dependencies for your distribution, then run:"
+ log_info " cd $INSTALL_DIR && npx playwright install chromium"
+ log_info "Browser tools will not work until dependencies are installed."
+ cd "$INSTALL_DIR" && npx playwright install chromium 2>/dev/null || true
+ ;;
+ esac
+ log_success "Browser engine setup complete"
+ fi
+
+ # Install TUI dependencies
+ if [ -f "$INSTALL_DIR/ui-tui/package.json" ]; then
+ log_info "Installing TUI dependencies..."
+ cd "$INSTALL_DIR/ui-tui"
+ npm install --silent 2>/dev/null || {
+ log_warn "TUI npm install failed (hermes --tui may not work)"
+ }
+ log_success "TUI dependencies installed"
+ fi
+
+ # Install WhatsApp bridge dependencies
+ if [ -f "$INSTALL_DIR/scripts/whatsapp-bridge/package.json" ]; then
+ log_info "Installing WhatsApp bridge dependencies..."
+ cd "$INSTALL_DIR/scripts/whatsapp-bridge"
+ npm install --silent 2>/dev/null || {
+ log_warn "WhatsApp bridge npm install failed (WhatsApp may not work)"
+ }
+ log_success "WhatsApp bridge dependencies installed"
+ fi
+}
+
+run_setup_wizard() {
+ if [ "$RUN_SETUP" = false ]; then
+ log_info "Skipping setup wizard (--skip-setup)"
+ return 0
+ fi
+
+ # The setup wizard reads from /dev/tty, so it works even when the
+ # install script itself is piped (curl | bash). Only skip if no
+ # terminal is available at all (e.g. Docker build, CI).
+ if ! [ -e /dev/tty ]; then
+ log_info "Setup wizard skipped (no terminal available). Run 'hermes setup' after install."
+ return 0
+ fi
+
+ echo ""
+ log_info "Starting setup wizard..."
+ echo ""
+
+ cd "$INSTALL_DIR"
+
+ # Run hermes setup using the venv Python directly (no activation needed).
+ # Redirect stdin from /dev/tty so interactive prompts work when piped from curl.
+ if [ "$USE_VENV" = true ]; then
+ "$INSTALL_DIR/venv/bin/python" -m hermes_cli.main setup < /dev/tty
+ else
+ python -m hermes_cli.main setup < /dev/tty
+ fi
+}
+
+maybe_start_gateway() {
+ # Check if any messaging platform tokens were configured
+ ENV_FILE="$HERMES_HOME/.env"
+ if [ ! -f "$ENV_FILE" ]; then
+ return 0
+ fi
+
+ HAS_MESSAGING=false
+ for VAR in TELEGRAM_BOT_TOKEN DISCORD_BOT_TOKEN SLACK_BOT_TOKEN SLACK_APP_TOKEN WHATSAPP_ENABLED; do
+ VAL=$(grep "^${VAR}=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2-)
+ if [ -n "$VAL" ] && [ "$VAL" != "your-token-here" ]; then
+ HAS_MESSAGING=true
+ break
+ fi
+ done
+
+ if [ "$HAS_MESSAGING" = false ]; then
+ return 0
+ fi
+
+ echo ""
+ log_info "Messaging platform token detected!"
+ log_info "The gateway needs to be running for Hermes to send/receive messages."
+
+ # If WhatsApp is enabled and no session exists yet, run foreground first for QR scan
+ WHATSAPP_VAL=$(grep "^WHATSAPP_ENABLED=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2-)
+ WHATSAPP_SESSION="$HERMES_HOME/whatsapp/session/creds.json"
+ if [ "$WHATSAPP_VAL" = "true" ] && [ ! -f "$WHATSAPP_SESSION" ]; then
+ if [ "$IS_INTERACTIVE" = true ]; then
+ echo ""
+ log_info "WhatsApp is enabled but not yet paired."
+ log_info "Running 'hermes whatsapp' to pair via QR code..."
+ echo ""
+ if prompt_yes_no "Pair WhatsApp now?" "yes"; then
+ HERMES_CMD="$(get_hermes_command_path)"
+ $HERMES_CMD whatsapp || true
+ fi
+ else
+ log_info "WhatsApp pairing skipped (non-interactive). Run 'hermes whatsapp' to pair."
+ fi
+ fi
+
+ if ! [ -e /dev/tty ]; then
+ log_info "Gateway setup skipped (no terminal available). Run 'hermes gateway install' later."
+ return 0
+ fi
+
+ echo ""
+ local should_install_gateway=false
+ if [ "$DISTRO" = "termux" ]; then
+ if prompt_yes_no "Would you like to start the gateway in the background?" "yes"; then
+ should_install_gateway=true
+ fi
+ else
+ if prompt_yes_no "Would you like to install the gateway as a background service?" "yes"; then
+ should_install_gateway=true
+ fi
+ fi
+
+ if [ "$should_install_gateway" = true ]; then
+ HERMES_CMD="$(get_hermes_command_path)"
+
+ if [ "$DISTRO" != "termux" ] && command -v systemctl &> /dev/null; then
+ log_info "Installing systemd service..."
+ if $HERMES_CMD gateway install 2>/dev/null; then
+ log_success "Gateway service installed"
+ if $HERMES_CMD gateway start 2>/dev/null; then
+ log_success "Gateway started! Your bot is now online."
+ else
+ log_warn "Service installed but failed to start. Try: hermes gateway start"
+ fi
+ else
+ log_warn "Systemd install failed. You can start manually: hermes gateway"
+ fi
+ else
+ if [ "$DISTRO" = "termux" ]; then
+ log_info "Termux detected �� starting gateway in best-effort background mode..."
+ else
+ log_info "systemd not available �� starting gateway in background..."
+ fi
+ nohup $HERMES_CMD gateway > "$HERMES_HOME/logs/gateway.log" 2>&1 &
+ GATEWAY_PID=$!
+ log_success "Gateway started (PID $GATEWAY_PID). Logs: ~/.hermes/logs/gateway.log"
+ log_info "To stop: kill $GATEWAY_PID"
+ log_info "To restart later: hermes gateway"
+ if [ "$DISTRO" = "termux" ]; then
+ log_warn "Android may stop background processes when Termux is suspended or the system reclaims resources."
+ fi
+ fi
+ else
+ log_info "Skipped. Start the gateway later with: hermes gateway"
+ fi
+}
+
+print_success() {
+ echo ""
+ echo -e "${GREEN}${BOLD}"
+ echo "����������������������������������������������������������������������������������������������������������������������"
+ echo "�� ? Installation Complete! ��"
+ echo "����������������������������������������������������������������������������������������������������������������������"
+ echo -e "${NC}"
+ echo ""
+
+ # Show file locations
+ echo -e "${CYAN}${BOLD}?? Your files (all in ~/.hermes/):${NC}"
+ echo ""
+ echo -e " ${YELLOW}Config:${NC} ~/.hermes/config.yaml"
+ echo -e " ${YELLOW}API Keys:${NC} ~/.hermes/.env"
+ echo -e " ${YELLOW}Data:${NC} ~/.hermes/cron/, sessions/, logs/"
+ echo -e " ${YELLOW}Code:${NC} ~/.hermes/hermes-agent/"
+ echo ""
+
+ echo -e "${CYAN}������������������������������������������������������������������������������������������������������������������${NC}"
+ echo ""
+ echo -e "${CYAN}${BOLD}?? Commands:${NC}"
+ echo ""
+ echo -e " ${GREEN}hermes${NC} Start chatting"
+ echo -e " ${GREEN}hermes setup${NC} Configure API keys & settings"
+ echo -e " ${GREEN}hermes config${NC} View/edit configuration"
+ echo -e " ${GREEN}hermes config edit${NC} Open config in editor"
+ echo -e " ${GREEN}hermes gateway install${NC} Install gateway service (messaging + cron)"
+ echo -e " ${GREEN}hermes update${NC} Update to latest version"
+ echo ""
+
+ echo -e "${CYAN}������������������������������������������������������������������������������������������������������������������${NC}"
+ echo ""
+ if [ "$DISTRO" = "termux" ]; then
+ echo -e "${YELLOW}? 'hermes' was linked into $(get_command_link_display_dir), which is already on PATH in Termux.${NC}"
+ echo ""
+ else
+ echo -e "${YELLOW}? Reload your shell to use 'hermes' command:${NC}"
+ echo ""
+ LOGIN_SHELL="$(basename "${SHELL:-/bin/bash}")"
+ if [ "$LOGIN_SHELL" = "zsh" ]; then
+ echo " source ~/.zshrc"
+ elif [ "$LOGIN_SHELL" = "bash" ]; then
+ echo " source ~/.bashrc"
+ elif [ "$LOGIN_SHELL" = "fish" ]; then
+ echo " source ~/.config/fish/config.fish"
+ else
+ echo " source ~/.bashrc # or ~/.zshrc"
+ fi
+ echo ""
+ fi
+
+ # Show Node.js warning if auto-install failed
+ if [ "$HAS_NODE" = false ]; then
+ echo -e "${YELLOW}"
+ echo "Note: Node.js could not be installed automatically."
+ echo "Browser tools need Node.js. Install manually:"
+ if [ "$DISTRO" = "termux" ]; then
+ echo " pkg install nodejs"
+ else
+ echo " https://nodejs.org/en/download/"
+ fi
+ echo -e "${NC}"
+ fi
+
+ # Show ripgrep note if not installed
+ if [ "$HAS_RIPGREP" = false ]; then
+ echo -e "${YELLOW}"
+ echo "Note: ripgrep (rg) was not found. File search will use"
+ echo "grep as a fallback. For faster search in large codebases,"
+ if [ "$DISTRO" = "termux" ]; then
+ echo "install ripgrep: pkg install ripgrep"
+ else
+ echo "install ripgrep: sudo apt install ripgrep (or brew install ripgrep)"
+ fi
+ echo -e "${NC}"
+ fi
+}
+
+# ============================================================================
+# Main
+# ============================================================================
+
+main() {
+ print_banner
+
+ detect_os
+ install_uv
+ check_python
+ if [ "$USE_LOCAL_DIR" = false ]; then
+ check_git
+ else
+ log_info "Using --local mode: skipping Git check"
+ fi
+ check_node
+ install_system_packages
+
+ clone_repo
+ setup_venv
+ install_deps
+ install_node_deps
+ setup_path
+ copy_config_templates
+ run_setup_wizard
+ maybe_start_gateway
+
+ print_success
+}
+
+main
\ No newline at end of file
diff --git a/scripts/install.sh b/scripts/install.sh
index 166d984fa..d0768bd6f 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -37,6 +37,7 @@ NODE_VERSION="22"
USE_VENV=true
RUN_SETUP=true
BRANCH="main"
+USE_LOCAL_DIR=false
# Detect non-interactive mode (e.g. curl | bash)
# When stdin is not a terminal, read -p will fail with EOF,
@@ -66,6 +67,10 @@ while [[ $# -gt 0 ]]; do
INSTALL_DIR="$2"
shift 2
;;
+ --local)
+ USE_LOCAL_DIR=true
+ shift
+ ;;
--hermes-home)
HERMES_HOME="$2"
shift 2
@@ -80,6 +85,7 @@ while [[ $# -gt 0 ]]; do
echo " --skip-setup Skip interactive setup wizard"
echo " --branch NAME Git branch to install (default: main)"
echo " --dir PATH Installation directory (default: ~/.hermes/hermes-agent)"
+ echo " --local Use existing local directory (--dir), skip git clone (for offline/China users)"
echo " --hermes-home PATH Data directory (default: ~/.hermes, or \$HERMES_HOME)"
echo " -h, --help Show this help"
exit 0
@@ -717,6 +723,28 @@ show_manual_install_hint() {
clone_repo() {
log_info "Installing to $INSTALL_DIR..."
+ if [ "$USE_LOCAL_DIR" = true ]; then
+ log_info "Using --local mode: skipping git clone, using existing directory..."
+
+ if [ ! -d "$INSTALL_DIR" ]; then
+ log_error "Directory not found: $INSTALL_DIR"
+ log_info "Please specify an existing directory with --dir PATH"
+ log_info "Or download the repository first and point --dir to it"
+ exit 1
+ fi
+
+ if [ ! -f "$INSTALL_DIR/pyproject.toml" ]; then
+ log_error "Directory does not appear to be a hermes-agent repository"
+ log_info "Expected to find pyproject.toml in: $INSTALL_DIR"
+ log_info "Please verify the directory contains the hermes-agent source code"
+ exit 1
+ fi
+
+ cd "$INSTALL_DIR"
+ log_success "Using local directory: $INSTALL_DIR"
+ return 0
+ fi
+
if [ -d "$INSTALL_DIR" ]; then
if [ -d "$INSTALL_DIR/.git" ]; then
log_info "Existing installation found, updating..."
@@ -768,7 +796,8 @@ clone_repo() {
fi
else
log_error "Directory exists but is not a git repository: $INSTALL_DIR"
- log_info "Remove it or choose a different directory with --dir"
+ log_info "To use an existing non-git directory, add --local flag"
+ log_info "Example: bash install.sh --local --dir $INSTALL_DIR"
exit 1
fi
else
@@ -786,6 +815,9 @@ clone_repo() {
log_success "Cloned via HTTPS"
else
log_error "Failed to clone repository"
+ log_info "For users in China or with network issues:"
+ log_info " 1. Download the repository manually (e.g., from Gitee or GitHub mirror)"
+ log_info " 2. Run: bash install.sh --local --dir /path/to/downloaded/hermes-agent"
exit 1
fi
fi
@@ -1417,7 +1449,11 @@ main() {
detect_os
install_uv
check_python
- check_git
+ if [ "$USE_LOCAL_DIR" = false ]; then
+ check_git
+ else
+ log_info "Using --local mode: skipping Git check"
+ fi
check_node
install_system_packages
diff --git a/ui-tui/package-lock.json b/ui-tui/package-lock.json
index 522b416e5..8b353ee62 100644
--- a/ui-tui/package-lock.json
+++ b/ui-tui/package-lock.json
@@ -989,8 +989,101 @@
}
},
"node_modules/@hermes/ink": {
- "resolved": "packages/hermes-ink",
- "link": true
+ "version": "0.0.1",
+ "resolved": "file:packages/hermes-ink",
+ "dependencies": {
+ "@alcalzone/ansi-tokenize": "^0.1.0",
+ "auto-bind": "^5.0.0",
+ "bidi-js": "^1.0.0",
+ "chalk": "^5.4.0",
+ "cli-boxes": "^3.0.0",
+ "code-excerpt": "^4.0.0",
+ "emoji-regex": "^10.4.0",
+ "get-east-asian-width": "^1.3.0",
+ "indent-string": "^5.0.0",
+ "lodash-es": "^4.17.0",
+ "react": ">=19.0.0",
+ "react-reconciler": "0.33.0",
+ "semver": "^7.6.0",
+ "signal-exit": "^4.1.0",
+ "stack-utils": "^2.0.0",
+ "strip-ansi": "^7.1.0",
+ "supports-hyperlinks": "^3.1.0",
+ "type-fest": "^4.30.0",
+ "usehooks-ts": "^3.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "peerDependencies": {
+ "ink-text-input": ">=6.0.0",
+ "react": ">=19.0.0"
+ }
+ },
+ "node_modules/@hermes/ink/node_modules/@alcalzone/ansi-tokenize": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz",
+ "integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "is-fullwidth-code-point": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14.13.1"
+ }
+ },
+ "node_modules/@hermes/ink/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@hermes/ink/node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@hermes/ink/node_modules/is-fullwidth-code-point": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
+ "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@hermes/ink/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@hermes/ink/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
@@ -6666,596 +6759,6 @@
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
- },
- "packages/hermes-ink": {
- "name": "@hermes/ink",
- "version": "0.0.1",
- "dependencies": {
- "@alcalzone/ansi-tokenize": "^0.1.0",
- "auto-bind": "^5.0.0",
- "bidi-js": "^1.0.0",
- "chalk": "^5.4.0",
- "cli-boxes": "^3.0.0",
- "code-excerpt": "^4.0.0",
- "emoji-regex": "^10.4.0",
- "get-east-asian-width": "^1.3.0",
- "indent-string": "^5.0.0",
- "lodash-es": "^4.17.0",
- "react": ">=19.0.0",
- "react-reconciler": "0.33.0",
- "semver": "^7.6.0",
- "signal-exit": "^4.1.0",
- "stack-utils": "^2.0.0",
- "strip-ansi": "^7.1.0",
- "supports-hyperlinks": "^3.1.0",
- "type-fest": "^4.30.0",
- "usehooks-ts": "^3.1.0",
- "wrap-ansi": "^9.0.0"
- },
- "devDependencies": {
- "esbuild": "^0.25.0"
- },
- "peerDependencies": {
- "ink-text-input": ">=6.0.0",
- "react": ">=19.0.0"
- }
- },
- "packages/hermes-ink/node_modules/@alcalzone/ansi-tokenize": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz",
- "integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.1",
- "is-fullwidth-code-point": "^4.0.0"
- },
- "engines": {
- "node": ">=14.13.1"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
- "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/android-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
- "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/android-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
- "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/android-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
- "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
- "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/darwin-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
- "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
- "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
- "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/linux-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
- "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/linux-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
- "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/linux-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
- "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/linux-loong64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
- "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
- "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
- "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
- "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/linux-s390x": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
- "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/linux-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
- "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
- "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
- "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/openharmony-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
- "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/sunos-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
- "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/win32-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
- "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/win32-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
- "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/@esbuild/win32-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
- "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "packages/hermes-ink/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "packages/hermes-ink/node_modules/chalk": {
- "version": "5.6.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
- "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
- "license": "MIT",
- "engines": {
- "node": "^12.17.0 || ^14.13 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "packages/hermes-ink/node_modules/esbuild": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
- "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.12",
- "@esbuild/android-arm": "0.25.12",
- "@esbuild/android-arm64": "0.25.12",
- "@esbuild/android-x64": "0.25.12",
- "@esbuild/darwin-arm64": "0.25.12",
- "@esbuild/darwin-x64": "0.25.12",
- "@esbuild/freebsd-arm64": "0.25.12",
- "@esbuild/freebsd-x64": "0.25.12",
- "@esbuild/linux-arm": "0.25.12",
- "@esbuild/linux-arm64": "0.25.12",
- "@esbuild/linux-ia32": "0.25.12",
- "@esbuild/linux-loong64": "0.25.12",
- "@esbuild/linux-mips64el": "0.25.12",
- "@esbuild/linux-ppc64": "0.25.12",
- "@esbuild/linux-riscv64": "0.25.12",
- "@esbuild/linux-s390x": "0.25.12",
- "@esbuild/linux-x64": "0.25.12",
- "@esbuild/netbsd-arm64": "0.25.12",
- "@esbuild/netbsd-x64": "0.25.12",
- "@esbuild/openbsd-arm64": "0.25.12",
- "@esbuild/openbsd-x64": "0.25.12",
- "@esbuild/openharmony-arm64": "0.25.12",
- "@esbuild/sunos-x64": "0.25.12",
- "@esbuild/win32-arm64": "0.25.12",
- "@esbuild/win32-ia32": "0.25.12",
- "@esbuild/win32-x64": "0.25.12"
- }
- },
- "packages/hermes-ink/node_modules/is-fullwidth-code-point": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
- "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "packages/hermes-ink/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "packages/hermes-ink/node_modules/type-fest": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
- "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
}
}
}