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 +

+ +## 简介 + +**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" - } } } }