diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6eb3d2..75c725e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -126,8 +126,143 @@ jobs: HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" python3 "$root/home/.agentboot/app/core/menu.py" uninstall codex test ! -e "$root/home/.agentboot/agents/codex" + extended-linux: + runs-on: ${{ vars.AGENTBOOT_LINUX_RUNNER || 'ubuntu-latest' }} + env: + TMPDIR: ${{ runner.temp }} + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + - name: Build and smoke OpenCode Linux offline package + shell: bash + run: | + AGENTS=opencode PLATFORMS=linux-x64 TAG="v$(cat VERSION)" sh scripts/build-offline.sh + root="$(mktemp -d)" + mkdir -p "$root/extract" "$root/home" + tag="v$(cat VERSION)" + tar -xzf "dist/AgentBoot-offline-${tag}-linux-x64.tar.gz" -C "$root/extract" + app="$root/extract/AgentBoot" + HOME="$root/home" sh "$app/install-offline.sh" opencode + HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" "$root/home/.agentboot/bin/opencode" --version + HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" python3 "$root/home/.agentboot/app/core/menu.py" uninstall opencode + test ! -e "$root/home/.agentboot/agents/opencode" + test -f "$root/home/.config/opencode/opencode.json" + mv "dist/AgentBoot-offline-${tag}-linux-x64.tar.gz" "dist/AgentBoot-offline-${tag}-linux-x64-opencode.tar.gz" + mv "dist/AgentBoot-offline-${tag}-linux-x64-sfx.sh" "dist/AgentBoot-offline-${tag}-linux-x64-opencode-sfx.sh" + - name: Install verify and uninstall official Cursor AppImage + shell: bash + run: | + root="$(mktemp -d)" + HOME="$root" AGENTBOOT_HOME="$root/.agentboot" python3 core/menu.py install cursor + HOME="$root" "$root/.agentboot/apps/cursor/Cursor.AppImage" --appimage-version + HOME="$root" AGENTBOOT_HOME="$root/.agentboot" python3 core/menu.py uninstall cursor + test ! -e "$root/.agentboot/apps/cursor" + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: extended-linux + path: dist/AgentBoot-offline-*-linux-x64-opencode* + + extended-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + - name: Build and smoke OpenCode Windows offline package + shell: powershell + run: | + $tag = 'v' + (Get-Content VERSION -Raw).Trim() + .\scripts\build-offline.ps1 -Tag $tag -Platforms win-x64 -Agents opencode + $root = Join-Path $env:RUNNER_TEMP 'opencode-smoke' + $extract = Join-Path $root 'extract' + $opencodeHome = Join-Path $root 'home' + New-Item -ItemType Directory -Path $extract, $opencodeHome -Force | Out-Null + tar -xf "dist\AgentBoot-offline-$tag-win-x64.zip" -C $extract + $app = Join-Path $extract 'AgentBoot' + $env:AGENTBOOT_HOME = Join-Path $opencodeHome '.agentboot' + $env:USERPROFILE = $opencodeHome + $env:LOCALAPPDATA = Join-Path $opencodeHome 'LocalAppData' + & "$app\install-offline.ps1" -Agents opencode + & "$opencodeHome\.agentboot\bin\opencode.cmd" --version + python "$env:LOCALAPPDATA\AgentBoot\app\core\menu.py" uninstall opencode + if (Test-Path "$opencodeHome\.agentboot\agents\opencode") { throw 'OpenCode payload not removed' } + if (-not (Test-Path "$opencodeHome\.config\opencode\opencode.json")) { throw 'OpenCode config was not preserved' } + Move-Item "dist\AgentBoot-offline-$tag-win-x64.zip" "dist\AgentBoot-offline-$tag-win-x64-opencode.zip" + - name: Install verify and uninstall official Cursor user app + shell: powershell + run: | + $root = Join-Path $env:RUNNER_TEMP 'cursor-smoke' + New-Item -ItemType Directory -Path $root -Force | Out-Null + $env:AGENTBOOT_HOME = Join-Path $root '.agentboot' + $env:USERPROFILE = $root + python core/menu.py install cursor + $exe = Join-Path $root '.agentboot\apps\cursor\Cursor.exe' + if (-not (Test-Path $exe)) { throw 'Cursor.exe missing' } + $signature = Get-AuthenticodeSignature -LiteralPath $exe + if ($signature.Status -ne 'Valid') { throw 'Cursor.exe signature invalid' } + python core/menu.py uninstall cursor + if (Test-Path (Join-Path $root '.agentboot\apps\cursor')) { throw 'Cursor managed app not removed' } + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: extended-windows + path: dist/AgentBoot-offline-*-win-x64-opencode.zip + + extended-macos-x64: + runs-on: macos-15-intel + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + - name: Build and smoke OpenCode plus Cursor on Intel macOS + shell: bash + run: | + AGENTS=opencode PLATFORMS=darwin-x64 TAG="v$(cat VERSION)" sh scripts/build-offline.sh + root="$(mktemp -d)"; mkdir -p "$root/extract" "$root/home" + tag="v$(cat VERSION)"; tar -xzf "dist/AgentBoot-offline-${tag}-darwin-x64.tar.gz" -C "$root/extract" + HOME="$root/home" sh "$root/extract/AgentBoot/install-offline.sh" opencode + HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" "$root/home/.agentboot/bin/opencode" --version + HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" python3 "$root/home/.agentboot/app/core/menu.py" uninstall opencode + HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" python3 core/menu.py install cursor + test -d "$root/home/.agentboot/apps/cursor/Cursor.app" + HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" python3 core/menu.py uninstall cursor + test ! -e "$root/home/.agentboot/apps/cursor" + mv "dist/AgentBoot-offline-${tag}-darwin-x64.tar.gz" "dist/AgentBoot-offline-${tag}-darwin-x64-opencode.tar.gz" + mv "dist/AgentBoot-offline-${tag}-darwin-x64-sfx.sh" "dist/AgentBoot-offline-${tag}-darwin-x64-opencode-sfx.sh" + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: extended-macos-x64 + path: dist/AgentBoot-offline-*-darwin-x64-opencode* + + extended-macos-arm64: + runs-on: macos-15 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + - name: Build and smoke OpenCode plus Cursor on Apple Silicon + shell: bash + run: | + test "$(uname -m)" = arm64 + AGENTS=opencode PLATFORMS=darwin-arm64 TAG="v$(cat VERSION)" sh scripts/build-offline.sh + root="$(mktemp -d)"; mkdir -p "$root/extract" "$root/home" + tag="v$(cat VERSION)"; tar -xzf "dist/AgentBoot-offline-${tag}-darwin-arm64.tar.gz" -C "$root/extract" + HOME="$root/home" sh "$root/extract/AgentBoot/install-offline.sh" opencode + HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" "$root/home/.agentboot/bin/opencode" --version + HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" python3 "$root/home/.agentboot/app/core/menu.py" uninstall opencode + HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" python3 core/menu.py install cursor + test -d "$root/home/.agentboot/apps/cursor/Cursor.app" + HOME="$root/home" AGENTBOOT_HOME="$root/home/.agentboot" python3 core/menu.py uninstall cursor + test ! -e "$root/home/.agentboot/apps/cursor" + mv "dist/AgentBoot-offline-${tag}-darwin-arm64.tar.gz" "dist/AgentBoot-offline-${tag}-darwin-arm64-opencode.tar.gz" + mv "dist/AgentBoot-offline-${tag}-darwin-arm64-sfx.sh" "dist/AgentBoot-offline-${tag}-darwin-arm64-opencode-sfx.sh" + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: extended-macos-arm64 + path: dist/AgentBoot-offline-*-darwin-arm64-opencode* + publish: - needs: [validate, online, offline-linux, offline-windows, macos-smoke] + needs: [validate, online, offline-linux, offline-windows, macos-smoke, extended-linux, extended-windows, extended-macos-x64, extended-macos-arm64] if: github.ref_type == 'tag' runs-on: ${{ vars.AGENTBOOT_LINUX_RUNNER || 'ubuntu-latest' }} environment: release-production diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c65225..2c706f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # 更新日志 +## v1.3.0 (2026-08-31) + +- 新增 Cursor 官方跨平台用户级安装:Linux AppImage 固定 SHA-256,Windows Authenticode,macOS codesign + notarization 校验。 +- Cursor 安装在 AgentBoot 私有应用目录,具备原子升级、归属 marker、安全卸载与外部安装保护。 +- OpenCode 固定 `opencode-ai@1.18.25`,离线包携带官方平台 native binary;x64 同时提供 regular 与 baseline 兼容版本。 +- OpenCode 目标机根据 `--version` 实测选择可运行 binary,不执行联网 postinstall,卸载保留模型配置。 +- OpenCode-only 离线包不再捆绑不需要的 Node 运行时,降低体积。 +- 新增 Linux、Windows、Intel macOS、Apple Silicon 的 OpenCode 离线生命周期与 Cursor 官方安装 CI 门禁。 + ## v1.2.0 (2026-08-31) - 全面重构中英文主页:亮色乐观视觉、桌面/手机自适应、可访问性、品牌 404 与单一 canonical。 diff --git a/README.en.md b/README.en.md index 64d8647..a90121b 100644 --- a/README.en.md +++ b/README.en.md @@ -5,7 +5,7 @@ **Minimal, fast, ready-to-run AI Agent launcher** [![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20macOS%20%7C%20Windows-blue)](#one-command-install) -[![Agents](https://img.shields.io/badge/Agents-14_indigo)](#supported-agents) +[![Agents](https://img.shields.io/badge/Agents-15_indigo)](#supported-agents) [![Model](https://img.shields.io/badge/Default_model-Agnes_free-orange)](#models) [![License](https://img.shields.io/badge/License-MIT-green)](LICENSE) @@ -48,22 +48,22 @@ Two commands after install: | | | |---|---| -| 📦 **Choose what to install** | 14 mainstream agents, multi-select in the menu | +| 📦 **Choose what to install** | 15 mainstream agents, multi-select in the menu | | 🛟 **Built-in fallback agent** | `ab`: zero third-party dependencies, Agnes by default, offline Linux knowledge base, session persistence | | 🧠 **Model provider manager** | Named custom providers, Ollama/LM Studio presets, failover order, connectivity test | | 🇨🇳 **China network adaptive** | npmmirror / Node mirrors / Tsinghua PyPI, Worker/Pages/Release fallback, proxy support | -| 📴 **Verified offline packages** | Releases provide Codex slim packs tested through install/run/uninstall; menu `[7]` builds other Agents on their target platform | +| 📴 **Verified offline packages** | Releases provide Codex and OpenCode slim packs tested through install/run/uninstall; menu `[7]` builds other Agents on their target platform | | ➕ **Custom agents** | Add anything beyond the registry (npm / pip / script), stored in your home dir | | 🧹 **Safe uninstall** | Menu `[9]` or `agentboot uninstall `; removes owned program files and preserves user data by default | | 🔐 **Verified install** | Enforced SHA-256, atomic app switching, rollback; custom scripts require HTTPS and avoid shell interpolation | | ⚡ **Measured performance** | TLS connection reuse, pre-indexed KB, and an on-device `/bench` for current network/model conditions | -## Supported agents (14) +## Supported agents (15) | # | Agent | Command | Vendor | Offline | |---|---|---|---|---| | 1 | CoCo Agent | `coco` | BitCook | Linux/macOS | -| 2 | OpenCode | `opencode` | opencode.ai | online only (postinstall not yet offline-verified) | +| 2 | OpenCode | `opencode` | opencode.ai | ✓ (pinned native binary; x64 baseline fallback) | | 3 | Hermes Agent | `hermes` | Hermes | ✓ (needs Git) | | 4 | Cline CLI | `cline` | Cline | ✓ | | 5 | CodeBuddy CLI | `codebuddy` | Tencent | ✓ | @@ -76,6 +76,7 @@ Two commands after install: | 12 | Gemini CLI | `gemini` | Google | ✓ | | 13 | iFlow CLI | `iflow` | iFlow | ✓ | | 14 | Aider | `aider` | Aider AI | online only (pip) | +| 15 | Cursor | `cursor` | Anysphere | online only (official signed desktop app in an AgentBoot-owned user directory) | Package names verified on the npm registry. `✓` = offline payload bundled. @@ -98,7 +99,7 @@ ab model # provider manager ## Offline packages -Download a platform-and-Agent-specific verified pack from [Releases](https://github.com/bit-cook/AgentBoot/releases). v1.2.0 initially publishes Linux x64 and Windows x64 Codex packs only after CI installs them, runs `codex --version`, and uninstalls them. Build other Agents on their target platform with menu `[7]` or: +Download a platform-and-Agent-specific verified pack from [Releases](https://github.com/bit-cook/AgentBoot/releases). v1.3.0 verifies OpenCode install, `--version`, and uninstall on Linux, Windows, Intel macOS, and Apple Silicon before uploading packs. Build other Agents on their target platform with menu `[7]` or: ```bash python core/menu.py build-offline win-x64 claude-code,pi diff --git a/README.md b/README.md index e95508b..2cd8a17 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ **极简 · 极速 · 开箱即用的 AI Agent 启动器** [![平台](https://img.shields.io/badge/平台-Linux%20%7C%20macOS%20%7C%20Windows-blue)](#一键在线安装) -[![Agent](https://img.shields.io/badge/自选_Agent-14_个-indigo)](#支持的-agent) +[![Agent](https://img.shields.io/badge/自选_Agent-15_个-indigo)](#支持的-agent) [![模型](https://img.shields.io/badge/默认模型-Agnes_免费-orange)](#模型开箱即用--完全自定义) [![License](https://img.shields.io/badge/License-MIT-green)](LICENSE) @@ -47,23 +47,23 @@ powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((New-Object Net.Web | | | |---|---| -| 📦 **菜单自选安装** | 14 个主流 Agent 按需勾选(见下表),支持命令行指定 | +| 📦 **菜单自选安装** | 15 个主流 Agent 按需勾选(见下表),支持命令行指定 | | 🛟 **内置保底 Agent** | `ab` 零第三方依赖 Python 核心:Agnes 开箱即用、离线 Linux 知识库、工具调用、会话持久化 | | 🧠 **提供商管理器** | Agnes 预设 + 自定义提供商命名管理 + Ollama/LM Studio 本地模型 + 故障切换顺序 | | 🇨🇳 **中国网络自适应** | npmmirror / Node 镜像 / 清华 PyPI 自动切换;Worker / Pages / Release 三源容错;代理一键配置 | -| 📴 **可验证离线包** | Release 提供经安装/启动/卸载冒烟的 Codex 精简包;菜单 `[7]` 可按目标平台自建其他 Agent 包 | +| 📴 **可验证离线包** | Release 提供经安装/启动/卸载冒烟的 Codex 与 OpenCode 精简包;菜单 `[7]` 可按目标平台自建其他 Agent 包 | | ➕ **自定义 Agent** | 菜单向导或 `add-agent` 添加注册表之外的任意 Agent(npm / pip / 脚本),用户目录保存、升级不丢 | | 🧹 **安全卸载** | 菜单 `[9]` 或 `agentboot uninstall `;精确清理程序,默认保留配置、认证与会话 | | 🔐 **可验证安装** | 在线包强制 SHA-256 校验、原子切换与失败回滚;自定义脚本仅 HTTPS 且无 shell 拼接 | | ⚡ **极致性能** | TLS 连接复用、知识库预建索引(热查询 <1ms)、上下文自动瘦身、流式中断保护、`/bench` 基准 | | 🪟 **三平台一致体验** | 同一套菜单、命令与文档;Windows 长路径与商店存根等细节已处理 | -## 支持的 Agent(14 个) +## 支持的 Agent(15 个) | # | Agent | 命令 | 厂商 | 离线 | 备注 | |---|---|---|---|---|---| | 1 | CoCo Agent | `coco` | BitCook | Linux/macOS | 官方脚本安装 | -| 2 | OpenCode | `opencode` | opencode.ai | 仅在线 | postinstall 尚未纳入离线验证 | +| 2 | OpenCode | `opencode` | opencode.ai | ✓ | 固定官方 native binary;x64 含 baseline 兼容包 | | 3 | Hermes Agent | `hermes` | Hermes | ✓ | 需 Git;国内自动镜像 | | 4 | Cline CLI | `cline` | Cline | ✓ | | | 5 | CodeBuddy CLI | `codebuddy` | Tencent | ✓ | | @@ -76,6 +76,7 @@ powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((New-Object Net.Web | 12 | Gemini CLI | `gemini` | Google | ✓ | | | 13 | iFlow CLI | `iflow` | iFlow 心流 | ✓ | | | 14 | Aider | `aider` | Aider AI | 仅在线 | pip 生态 | +| 15 | Cursor | `cursor` | Anysphere | 仅在线 | 官方签名桌面应用;AgentBoot 私有用户级安装 | 包名均已逐一在 npm registry 核实;`✓` = 离线包内置完整依赖与运行时。 @@ -99,7 +100,7 @@ ab bench # 性能基准 ## 离线安装与瘦身定制 -到 [Releases](https://github.com/bit-cook/AgentBoot/releases) 下载带平台与 Agent 名称的已验证精简包,拷到目标机解压后运行包内 `install-offline.ps1` / `sh install-offline.sh`。v1.2.0 首批发布 Linux x64 / Windows x64 的 Codex 包,发布流水线会实际执行安装、`codex --version` 和卸载后才上传。 +到 [Releases](https://github.com/bit-cook/AgentBoot/releases) 下载带平台与 Agent 名称的已验证精简包,拷到目标机解压后运行包内 `install-offline.ps1` / `sh install-offline.sh`。v1.3.0 发布流水线会在 Linux、Windows、Intel macOS 与 Apple Silicon 上实际验证 OpenCode 安装、`--version` 和卸载后才上传。 其他 Agent 使用菜单 `[7]` 或 `build-offline win-x64 claude-code,pi` 在目标平台按需构建。Hermes 含平台相关 Python venv,必须在对应平台构建;Aider 暂不支持离线。 diff --git a/VERSION b/VERSION index 26aaba0..f0bb29e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.0 +1.3.0 diff --git a/agents/registry.json b/agents/registry.json index e30244f..345bef4 100644 --- a/agents/registry.json +++ b/agents/registry.json @@ -22,12 +22,30 @@ "desc": "开源终端 AI 编码 Agent(TUI)", "bin": "opencode", "method": "npm", - "npm": "opencode-ai", + "npm": "opencode-ai@1.18.25", "node": ">=18", - "offline": false, + "offline": true, "npm_install_flags": ["--ignore-scripts"], + "offline_binary_packages": { + "linux-x64": ["opencode-linux-x64", "opencode-linux-x64-baseline"], + "linux-arm64": ["opencode-linux-arm64"], + "darwin-x64": ["opencode-darwin-x64", "opencode-darwin-x64-baseline"], + "darwin-arm64": ["opencode-darwin-arm64"], + "win-x64": ["opencode-windows-x64", "opencode-windows-x64-baseline"] + }, "script": "https://opencode.ai/install", - "notes": ["已预置 Agnes 免费模型(~/.config/opencode/opencode.json),安装即用", "也可用官方脚本安装:curl -fsSL https://opencode.ai/install | bash"] + "notes": ["已预置 Agnes 免费模型(~/.config/opencode/opencode.json),安装即用", "离线包同时携带官方原生 binary 与 x64 baseline 兼容包"] + }, + { + "id": "cursor", + "name": "Cursor", + "vendor": "Anysphere", + "desc": "Cursor 官方 AI 代码编辑器(用户级受管安装)", + "bin": "cursor", + "method": "cursor", + "special_install": "cursor", + "offline": false, + "notes": ["使用 Cursor 官方签名/固定版本资产,安装到 AgentBoot 私有用户目录", "不会接管或卸载系统中已有的外部 Cursor"] }, { "id": "hermes", diff --git a/cloudflare/web-assets.js b/cloudflare/web-assets.js index 4e75f05..8c39eec 100644 --- a/cloudflare/web-assets.js +++ b/cloudflare/web-assets.js @@ -1,2 +1,2 @@ // Generated by scripts/sync-web-assets.py; do not edit by hand. -export const WEB_ASSETS = Object.freeze({"/index.html":{"body":"\n\n\n \n \n \n \n AgentBoot · 一条命令,启动你的 AI Agent\n \n \n \n \n \n \n \n\n\n 跳到主要内容\n\n \n\n
\n
\n

v1.2.0 · 免费开源 · 三平台可用

\n

一条命令,
Agent 就位

\n

把安装、模型、镜像、离线包和安全卸载收进一个清楚的菜单。你选择要用的 AI Agent,AgentBoot 负责把它可靠地装好。

\n \n
    \n
  • 14 个 Agent 按需选择
  • \n
  • Linux · macOS · Windows
  • \n
  • 离线安装
  • \n
  • MIT License
  • \n
\n
\n\n
\n \n \n \n
\n
\n \n agentboot\n
\n
\n

$ agentboot

\n

检查系统环境与网络…

\n

选择你想安装的 Agent

\n

校验来源与 SHA-256

\n

安装完成,开始创造。

\n
\n
\n
\n
\n\n
\n
\n
\n
\n
\n

从这里开始

\n

复制一行,马上启动

\n

安装完成后输入 agentboot 打开控制台菜单,或输入 ab 直接与内置 Agent 对话。

\n
\n
\n \n \n \n

安装器仅使用项目控制的 Worker、GitHub Pages 与 GitHub Release,并对安装包执行同源 SHA-256 校验。

\n
\n
\n\n
\n
\n
\n Linux / macOS\n \n
\n curl -fsSL https://boot.ide.pub/install.sh | sh\n
\n\n
\n
\n Windows PowerShell\n \n
\n powershell -NoProfile -ExecutionPolicy Bypass -Command \"iex ((New-Object Net.WebClient).DownloadString('https://boot.ide.pub/install.ps1'))\"\n
\n
\n
\n\n
    \n
  1. 安装 AgentBoot

    上面一条命令约 10 秒完成,只安装 AgentBoot 本体与内置 Agent。

  2. \n
  3. 选择需要的 Agent

    运行 agentboot,在菜单中安装、卸载和配置。

  4. \n
  5. 开始工作

    运行选好的 Agent;也可以直接输入 ab 零配置对话。

  6. \n
\n
\n\n
\n
\n

不仅是安装脚本

复杂留在里面,选择留给你

\n

不塞给你一个全家桶,也不把失败留给你排查。AgentBoot 把最容易出错的环境、网络、来源与卸载边界统一处理。

\n
\n\n
\n
\n

始终可用的起点

\n

即使其他 Agent 装不上,ab 也能马上工作

\n

零第三方依赖 Python 核心,默认接入 Agnes 免费模型,并内置离线 Linux 知识库、会话持久化和高危命令拦截。

\n
\n\n
    \n
  1. 01

    按需安装,不装全家桶

    14 个主流 Agent 自由选择:Claude Code、Codex、Qwen Code、OpenCode、MiMo、Cline、Pi、CoCo 等。

  2. \n
  3. 02

    模型提供商统一管理

    Agnes 零配置可用,也支持命名管理任意 OpenAI 兼容接口、Ollama、LM Studio 与故障切换顺序。

  4. \n
  5. 03

    中国网络自动适配

    自动选择 npm、Node、PyPI 镜像,Worker、Pages、Release 三源容错,并提供一键代理配置。

  6. \n
  7. 04

    离线包经过真实验证

    发布包完成安装、启动、卸载冒烟;菜单 [7] 还能在目标平台按需构建自定义包。

  8. \n
  9. 05

    卸载知道自己该删什么

    菜单 [9]uninstall 只清理由 AgentBoot 管理的程序,默认保留配置、认证和会话。

  10. \n
  11. 06

    性能可以现场测量

    TLS 连接复用、知识库预建索引、上下文自动瘦身和流式中断保护;/bench 基于当前网络与模型实测。

  12. \n
\n
\n
\n\n
\n
\n
\n

你的工具箱

14 个 Agent,一个入口

\n

在线安装按需选择;标记为离线可用的 Agent 可连同依赖与运行时一起打包。

\n
\n\n
\n
\n
14个内置选项
\n 绿点表示支持离线安装\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
#Agent命令厂商离线
01CoCo AgentcocoBitCookLinux/macOS
02OpenCodeopencodeopencode.ai仅在线
03Hermes AgenthermesHermes需 Git
04Cline CLIclineCline支持
05CodeBuddy CLIcodebuddyTencent支持
06Pi Coding AgentpiEarendil Works支持
07Claude CodeclaudeAnthropic支持
08OpenAI Codex CLIcodexOpenAIAgnes 预置
09Qwen CodeqwenAlibabaAgnes 预置
10MiMo CodemimoXiaomi支持
11OpenClawopenclawOpenClaw支持
12Gemini CLIgeminiGoogle支持
13iFlow CLIiflowiFlow 心流支持
14AideraiderAider AI仅在线
\n
\n

Codex 与 Qwen 安装后自动预置 Agnes 免费模型;也可通过菜单 +add-agent 添加自己的 Agent。

\n
\n
\n
\n\n
\n
\n
\n

没有网络,也能安装

\n

可验证离线包,把完整运行环境一起带走

\n

v1.2.0 首批提供 Linux x64 与 Windows x64 Codex 精简包,CI 实际执行安装、版本启动与卸载。

\n
    \n
  • 已验证精简包:从 Releases 下载后直接带到目标机。
  • \n
  • 自建离线包:用菜单 [7]build-offline 在目标平台按需构建。
  • \n
  • 无需额外解压软件:运行包内 install-offline.ps1sh install-offline.sh
  • \n
\n
\n\n
\n

模型由你决定

\n

开箱即用,也完全允许自定义

\n
    \n
  • Agnes 免费模型:ab 零配置直接用,Codex 与 Qwen 自动接线。
  • \n
  • 自定义提供商:任意 OpenAI 兼容接口,命名管理、随时切换。
  • \n
  • 本地模型:内置 Ollama 与 LM Studio 预设,也可添加 vLLM。
  • \n
  • 故障切换:设置备用顺序,主模型失败后自动降级。
  • \n
\n
\n
\n
\n\n
\n
\n
\n

需要更多细节

文档直接说人话

\n

从安装、离线部署到自定义 Agent,按你当前要解决的问题继续阅读。

\n
\n \n
\n
\n\n
\n
\n

少折腾环境,早点开始创造

\n

AgentBoot 是 MIT 许可的开源项目。先用一条命令开始,所有选择依然掌握在你手里。

\n \n
\n
\n
\n\n \n\n\n","type":"text/html; charset=utf-8","etag":"\"91786b20ada44a56\""},"/en/index.html":{"body":"\n\n\n \n \n \n \n AgentBoot · Launch your AI agents with one command\n \n \n \n \n \n \n \n\n\n Skip to main content\n\n \n\n
\n
\n

v1.2.0 · Free and open source · Three platforms

\n

One command.
Agents ready.

\n

Bring installation, models, mirrors, offline packs, and safe uninstall into one clear menu. You choose the AI agents you need; AgentBoot installs them reliably.

\n \n
    \n
  • Choose from 14 agents
  • \n
  • Linux · macOS · Windows
  • \n
  • Offline installation
  • \n
  • MIT License
  • \n
\n
\n\n
\n \n \n \n
\n
\n \n agentboot\n
\n
\n

$ agentboot

\n

Checking system and network…

\n

Choose the agents you want to install

\n

Verify source and SHA-256

\n

Ready. Start building.

\n
\n
\n
\n
\n\n
\n
\n
\n
\n
\n

Start here

\n

Copy one line. Start right away.

\n

After installation, run agentboot to open the console menu, or ab to chat directly with the built-in agent.

\n
\n
\n \n \n \n

The installer uses only project-controlled Worker, GitHub Pages, and GitHub Release sources, then verifies each installation package against a same-origin SHA-256 checksum.

\n
\n
\n\n
\n
\n
\n Linux / macOS\n \n
\n curl -fsSL https://boot.ide.pub/install.sh | sh\n
\n\n
\n
\n Windows PowerShell\n \n
\n powershell -NoProfile -ExecutionPolicy Bypass -Command \"iex ((New-Object Net.WebClient).DownloadString('https://boot.ide.pub/install.ps1'))\"\n
\n
\n
\n\n
    \n
  1. Install AgentBoot

    The command above takes about 10 seconds and installs only AgentBoot itself and the built-in agent.

  2. \n
  3. Choose the agents you need

    Run agentboot to install, uninstall, and configure agents from the menu.

  4. \n
  5. Get to work

    Launch your chosen agent, or run ab for a zero-configuration chat.

  6. \n
\n
\n\n
\n
\n

More than an install script

AgentBoot handles the complexity. You make the choices.

\n

No all-in-one bundle, and no failed setup left for you to debug. AgentBoot handles the parts most likely to break: environments, networks, sources, and uninstall boundaries.

\n
\n\n
\n
\n

A starting point that's always ready

\n

Even if other agents won't install, ab works right away

\n

The built-in agent uses a Python core with no third-party dependencies, the free Agnes model by default, an offline Linux knowledge base, persistent sessions, and safeguards against high-risk commands.

\n
\n\n
    \n
  1. 01

    Install on demand, not as a bundle

    Choose from 14 mainstream agents, including Claude Code, Codex, Qwen Code, OpenCode, MiMo, Cline, Pi, and CoCo.

  2. \n
  3. 02

    Manage model providers in one place

    Agnes works with zero configuration. You can also name and manage any OpenAI-compatible endpoint, Ollama, or LM Studio, and set a failover order.

  4. \n
  5. 03

    Automatically adapt to networks in China

    AgentBoot selects npm, Node, and PyPI mirrors automatically, fails over across Worker, Pages, and Release sources, and configures a proxy in one step.

  6. \n
  7. 04

    Offline packs are tested end to end

    Release packs pass install, launch, and uninstall smoke tests; menu [7] can also build a custom pack on demand on the target platform.

  8. \n
  9. 05

    Uninstall only what AgentBoot owns

    Menu [9] or uninstall removes AgentBoot-managed programs while keeping configuration, credentials, and sessions by default.

  10. \n
  11. 06

    Measure performance where it matters

    AgentBoot reuses TLS connections, prebuilds knowledge-base indexes, trims context automatically, and protects interrupted streams. /bench measures your current network and model.

  12. \n
\n
\n
\n\n
\n
\n
\n

Your toolbox

14 agents. One front door.

\n

Choose agents as needed for online installation. Agents marked as offline-ready can be packaged with their dependencies and runtimes.

\n
\n\n
\n
\n
14built-in choices
\n A green dot indicates support for offline installation\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
#AgentCommandVendorOffline
01CoCo AgentcocoBitCookLinux/macOS
02OpenCodeopencodeopencode.aiOnline only
03Hermes AgenthermesHermesRequires Git
04Cline CLIclineClineSupported
05CodeBuddy CLIcodebuddyTencentSupported
06Pi Coding AgentpiEarendil WorksSupported
07Claude CodeclaudeAnthropicSupported
08OpenAI Codex CLIcodexOpenAIAgnes preset
09Qwen CodeqwenAlibabaAgnes preset
10MiMo CodemimoXiaomiSupported
11OpenClawopenclawOpenClawSupported
12Gemini CLIgeminiGoogleSupported
13iFlow CLIiflowiFlowSupported
14AideraiderAider AIOnline only
\n
\n

Codex and Qwen are automatically configured with the free Agnes model after installation. Add your own agent with menu + or the add-agent command.

\n
\n
\n
\n\n
\n
\n
\n

Install without internet access

\n

Verified offline packs bring the complete runtime with you

\n

v1.2.0 initially provides slim Codex packs for Linux x64 and Windows x64. CI installs each pack, runs its version command, and uninstalls it.

\n
    \n
  • Verified slim packs: download from Releases and carry them to the target machine.
  • \n
  • Custom offline packs: use menu [7] or build-offline on the target platform.
  • \n
  • No extra extraction software: run install-offline.ps1 or sh install-offline.sh inside the pack.
  • \n
\n
\n\n
\n

Your models, your choice

\n

Ready out of the box and fully customizable

\n
    \n
  • Agnes free model: use ab with zero setup; Codex and Qwen are configured automatically.
  • \n
  • Custom providers: name, manage, and switch any OpenAI-compatible endpoint.
  • \n
  • Local models: Ollama and LM Studio presets, with vLLM available as a custom provider.
  • \n
  • Failover: set a fallback order and switch automatically when the primary fails.
  • \n
\n
\n
\n
\n\n
\n
\n
\n

More detail when you need it

Documentation that speaks plainly

\n

Start with the problem in front of you, from installation and offline deployment to custom agents.

\n
\n \n
\n
\n\n
\n
\n

Spend less time wrestling with setup. Start building sooner.

\n

AgentBoot is open source under the MIT License. Start with one command; every choice stays in your hands.

\n \n
\n
\n
\n\n \n\n\n","type":"text/html; charset=utf-8","etag":"\"e308bf93b5165b3d\""},"/404.html":{"body":"\n\n\n \n \n \n \n 页面未找到 · AgentBoot\n \n \n\n\n
\n
\n

404

\n

这条路径没有 Agent,回到启动台吧

\n

地址可能已变更,或只是多输入了一个字符。项目主页、安装命令和文档都还在。

\n \n
\n
\n\n\n","type":"text/html; charset=utf-8","etag":"\"92d469229bf5cde6\""},"/assets/site.css":{"body":":root {\n color-scheme: light;\n --ink-950: #12251d;\n --ink-800: #294238;\n --ink-600: #5b7067;\n --ink-400: #91a198;\n --paper: #fffdf5;\n --surface: #ffffff;\n --soft: #f2f7f2;\n --line: #dce7df;\n --green: #137a52;\n --green-dark: #0d5e3e;\n --green-soft: #e3f5eb;\n --sun: #ffd84d;\n --sun-soft: #fff3b9;\n --coral: #ff7a59;\n --sky: #dff3ff;\n --success: #137a52;\n --warning: #a65e00;\n --error: #b42318;\n --info: #176b87;\n --code: #10251d;\n --code-text: #ecfff4;\n --display: ui-rounded, \"Avenir Next Rounded\", \"SF Pro Rounded\", \"Segoe UI\", sans-serif;\n --body: Inter, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif;\n --mono: \"SFMono-Regular\", Consolas, \"Liberation Mono\", monospace;\n --ease: cubic-bezier(.2, .8, .2, 1);\n}\n\n* {\n box-sizing: border-box;\n}\n\nhtml {\n overflow-x: clip;\n scroll-behavior: smooth;\n scroll-padding-top: 88px;\n}\n\nbody {\n margin: 0;\n background: var(--paper);\n color: var(--ink-950);\n font-family: var(--body);\n font-size: 16px;\n line-height: 1.68;\n text-rendering: optimizeLegibility;\n -webkit-font-smoothing: antialiased;\n}\n\nbutton,\na {\n -webkit-tap-highlight-color: transparent;\n}\n\na {\n color: var(--green-dark);\n text-decoration-thickness: 1.5px;\n text-underline-offset: .2em;\n}\n\na:hover {\n color: var(--green);\n}\n\n:focus-visible {\n outline: 3px solid var(--sun);\n outline-offset: 3px;\n}\n\n.skip-link {\n position: fixed;\n z-index: 100;\n top: 12px;\n left: 12px;\n translate: 0 -150%;\n border: 1px solid var(--ink-950);\n border-radius: 10px;\n background: var(--surface);\n color: var(--ink-950);\n padding: 10px 14px;\n font-weight: 700;\n}\n\n.skip-link:focus {\n translate: 0;\n}\n\n.shell {\n width: min(1180px, calc(100% - 48px));\n margin-inline: auto;\n}\n\n.site-nav {\n position: relative;\n z-index: 10;\n display: flex;\n min-height: 76px;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n}\n\n.brand {\n display: inline-flex;\n min-height: 44px;\n align-items: center;\n gap: 11px;\n color: var(--ink-950);\n font-family: var(--display);\n font-size: 18px;\n font-weight: 800;\n letter-spacing: -.025em;\n text-decoration: none;\n}\n\n.brand-mark {\n position: relative;\n display: inline-grid;\n width: 34px;\n height: 34px;\n place-items: center;\n overflow: hidden;\n border: 2px solid var(--ink-950);\n border-radius: 11px;\n background: var(--sun);\n}\n\n.brand-mark::before,\n.brand-mark::after {\n content: \"\";\n position: absolute;\n height: 2px;\n border-radius: 999px;\n background: var(--ink-950);\n transform-origin: left center;\n}\n\n.brand-mark::before {\n width: 12px;\n translate: -1px -3px;\n rotate: 38deg;\n}\n\n.brand-mark::after {\n width: 10px;\n translate: 2px 5px;\n}\n\n.nav-links {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.nav-link,\n.language-link {\n display: inline-flex;\n min-height: 44px;\n align-items: center;\n justify-content: center;\n border-radius: 10px;\n color: var(--ink-800);\n padding: 8px 11px;\n font-size: 14px;\n font-weight: 650;\n text-decoration: none;\n}\n\n.nav-link:hover,\n.language-link:hover {\n background: var(--green-soft);\n color: var(--green-dark);\n}\n\n.language-link {\n border: 1px solid var(--line);\n background: rgba(255, 255, 255, .72);\n}\n\n.hero {\n position: relative;\n display: grid;\n min-height: 660px;\n align-items: center;\n grid-template-columns: minmax(0, 7fr) minmax(340px, 5fr);\n gap: clamp(32px, 6vw, 86px);\n padding-block: 72px 112px;\n}\n\n.hero::before {\n content: \"\";\n position: absolute;\n z-index: -2;\n top: 2%;\n right: 0;\n width: min(48vw, 620px);\n aspect-ratio: 1;\n border-radius: 50%;\n background: var(--sun-soft);\n}\n\n.hero::after {\n content: \"\";\n position: absolute;\n z-index: -1;\n bottom: 9%;\n left: 0;\n width: min(30vw, 380px);\n aspect-ratio: 1;\n border-radius: 50%;\n background: var(--sky);\n opacity: .7;\n}\n\n.eyebrow {\n display: inline-flex;\n align-items: center;\n gap: 9px;\n margin: 0 0 22px;\n color: var(--green-dark);\n font-size: 13px;\n font-weight: 750;\n letter-spacing: .08em;\n text-transform: uppercase;\n}\n\n.eyebrow::before {\n content: \"\";\n width: 9px;\n height: 9px;\n border-radius: 50%;\n background: var(--coral);\n box-shadow: 0 0 0 5px rgba(255, 122, 89, .14);\n}\n\nh1,\nh2,\nh3 {\n font-family: var(--display);\n text-wrap: balance;\n}\n\nh1 {\n max-width: 800px;\n margin: 0;\n font-size: clamp(48px, 7.1vw, 88px);\n font-weight: 800;\n letter-spacing: -.06em;\n line-height: .98;\n}\n\n.hero-highlight {\n position: relative;\n display: inline-block;\n white-space: nowrap;\n}\n\n.hero-highlight::after {\n content: \"\";\n position: absolute;\n z-index: -1;\n right: -.08em;\n bottom: .04em;\n left: -.02em;\n height: .24em;\n border-radius: 99px;\n background: var(--sun);\n rotate: -1.5deg;\n}\n\n.hero-copy {\n max-width: 650px;\n margin: 28px 0 0;\n color: var(--ink-600);\n font-size: clamp(17px, 2vw, 21px);\n line-height: 1.62;\n}\n\n.hero-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n margin-top: 34px;\n}\n\n.button {\n display: inline-flex;\n min-height: 48px;\n align-items: center;\n justify-content: center;\n gap: 9px;\n border: 1px solid var(--ink-950);\n border-radius: 12px;\n padding: 11px 17px;\n font-size: 15px;\n font-weight: 750;\n line-height: 1;\n text-decoration: none;\n transition: translate 160ms var(--ease), background-color 160ms var(--ease), color 160ms var(--ease);\n}\n\n.button-primary {\n background: var(--green);\n color: #fff;\n border-color: var(--green);\n}\n\n.button-primary:hover {\n background: var(--green-dark);\n color: #fff;\n translate: 0 -2px;\n}\n\n.button-secondary {\n background: var(--surface);\n color: var(--ink-950);\n}\n\n.button-secondary:hover {\n background: var(--sun-soft);\n color: var(--ink-950);\n translate: 0 -2px;\n}\n\n.button svg,\n.text-link svg {\n width: 18px;\n height: 18px;\n flex: 0 0 auto;\n fill: none;\n stroke: currentColor;\n stroke-linecap: round;\n stroke-linejoin: round;\n stroke-width: 1.8;\n}\n\n.hero-proof {\n display: flex;\n flex-wrap: wrap;\n gap: 10px 18px;\n margin: 26px 0 0;\n padding: 0;\n color: var(--ink-600);\n font-size: 13px;\n font-weight: 650;\n list-style: none;\n}\n\n.hero-proof li {\n display: flex;\n align-items: center;\n gap: 7px;\n}\n\n.hero-proof li::before {\n content: \"\";\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--green);\n}\n\n.launch-visual {\n position: relative;\n min-height: 450px;\n}\n\n.orbit {\n position: absolute;\n inset: 0;\n margin: auto;\n border: 1px solid rgba(18, 37, 29, .18);\n border-radius: 50%;\n}\n\n.orbit-one {\n width: min(100%, 430px);\n aspect-ratio: 1;\n}\n\n.orbit-two {\n width: min(76%, 330px);\n aspect-ratio: 1;\n border-style: dashed;\n}\n\n.orbit-dot {\n position: absolute;\n top: 10%;\n right: 14%;\n width: 18px;\n height: 18px;\n border: 3px solid var(--paper);\n border-radius: 50%;\n background: var(--coral);\n box-shadow: 0 0 0 1px var(--ink-950);\n}\n\n.terminal {\n position: absolute;\n inset: 50% auto auto 50%;\n width: min(94%, 390px);\n overflow: hidden;\n border: 2px solid var(--ink-950);\n border-radius: 22px;\n background: var(--code);\n box-shadow: 18px 18px 0 var(--sun);\n color: var(--code-text);\n translate: -50% -50%;\n}\n\n.terminal-bar {\n display: flex;\n min-height: 48px;\n align-items: center;\n justify-content: space-between;\n border-bottom: 1px solid rgba(255, 255, 255, .13);\n padding: 0 17px;\n color: #b9d3c4;\n font-family: var(--mono);\n font-size: 12px;\n}\n\n.terminal-dots {\n display: flex;\n gap: 6px;\n}\n\n.terminal-dots i {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--coral);\n}\n\n.terminal-dots i:nth-child(2) {\n background: var(--sun);\n}\n\n.terminal-dots i:nth-child(3) {\n background: #57d49b;\n}\n\n.terminal-body {\n min-height: 238px;\n padding: 26px 22px;\n font-family: var(--mono);\n font-size: clamp(12px, 1.35vw, 14px);\n line-height: 1.8;\n}\n\n.terminal-body p {\n margin: 0 0 12px;\n}\n\n.prompt {\n color: #69e4a7;\n}\n\n.terminal-muted {\n color: #9bb7a8;\n}\n\n.terminal-check {\n color: var(--sun);\n}\n\n.section {\n padding-block: clamp(72px, 10vw, 120px);\n}\n\n.section-soft {\n position: relative;\n background: var(--soft);\n}\n\n.section-soft::before,\n.section-soft::after {\n content: \"\";\n position: absolute;\n right: 0;\n left: 0;\n height: 1px;\n background: var(--line);\n}\n\n.section-soft::before {\n top: 0;\n}\n\n.section-soft::after {\n bottom: 0;\n}\n\n.section-heading {\n display: grid;\n align-items: end;\n grid-template-columns: minmax(0, 4fr) minmax(280px, 3fr);\n gap: 32px;\n margin-bottom: 54px;\n}\n\n.kicker {\n margin: 0 0 12px;\n color: var(--green);\n font-size: 13px;\n font-weight: 750;\n letter-spacing: .08em;\n text-transform: uppercase;\n}\n\nh2 {\n max-width: 700px;\n margin: 0;\n font-size: clamp(34px, 4.8vw, 56px);\n font-weight: 800;\n letter-spacing: -.045em;\n line-height: 1.06;\n}\n\n.section-lede {\n max-width: 56ch;\n margin: 0;\n color: var(--ink-600);\n font-size: 17px;\n}\n\n.install-layout {\n display: grid;\n align-items: start;\n grid-template-columns: minmax(240px, 4fr) minmax(0, 8fr);\n gap: clamp(34px, 7vw, 88px);\n}\n\n.install-intro {\n position: sticky;\n top: 104px;\n}\n\n.install-intro h2 {\n font-size: clamp(34px, 4vw, 48px);\n}\n\n.install-intro p {\n color: var(--ink-600);\n}\n\n.security-note {\n display: grid;\n grid-template-columns: 38px 1fr;\n gap: 13px;\n margin-top: 28px;\n padding-top: 22px;\n border-top: 1px solid var(--line);\n color: var(--ink-600);\n font-size: 14px;\n}\n\n.security-icon {\n display: grid;\n width: 38px;\n height: 38px;\n place-items: center;\n border-radius: 12px;\n background: var(--green-soft);\n color: var(--green-dark);\n}\n\n.security-icon svg {\n width: 21px;\n height: 21px;\n fill: none;\n stroke: currentColor;\n stroke-linecap: round;\n stroke-linejoin: round;\n stroke-width: 1.8;\n}\n\n.security-note p {\n margin: 0;\n}\n\n.command-stack {\n display: grid;\n gap: 16px;\n}\n\n.command-card {\n overflow: hidden;\n border: 1px solid var(--ink-950);\n border-radius: 20px;\n background: var(--surface);\n}\n\n.command-head {\n display: flex;\n min-height: 56px;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-bottom: 1px solid var(--line);\n padding: 10px 12px 10px 20px;\n}\n\n.command-platform {\n display: flex;\n align-items: center;\n gap: 11px;\n font-weight: 750;\n}\n\n.platform-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: var(--coral);\n}\n\n.command-card:nth-child(2) .platform-dot {\n background: #2f80ed;\n}\n\n.copy-button {\n display: inline-flex;\n min-width: 90px;\n min-height: 44px;\n align-items: center;\n justify-content: center;\n gap: 7px;\n border: 1px solid var(--line);\n border-radius: 10px;\n background: var(--soft);\n color: var(--ink-950);\n padding: 8px 12px;\n font: 700 13px/1 var(--body);\n cursor: pointer;\n transition: background-color 160ms var(--ease), border-color 160ms var(--ease);\n}\n\n.copy-button:hover {\n border-color: var(--green);\n background: var(--green-soft);\n}\n\n.copy-button[data-state=\"success\"] {\n border-color: var(--green);\n background: var(--green-soft);\n color: var(--green-dark);\n}\n\n.copy-button[data-state=\"error\"] {\n border-color: var(--error);\n background: #fff0ed;\n color: var(--error);\n}\n\n.copy-button svg {\n width: 16px;\n height: 16px;\n fill: none;\n stroke: currentColor;\n stroke-linecap: round;\n stroke-linejoin: round;\n stroke-width: 1.8;\n}\n\n.command-code {\n display: block;\n margin: 0;\n overflow-x: auto;\n background: var(--code);\n color: var(--code-text);\n padding: 24px 20px;\n font-family: var(--mono);\n font-size: 13px;\n line-height: 1.75;\n white-space: pre;\n scrollbar-color: #557266 var(--code);\n}\n\n.command-code::selection,\n.terminal ::selection {\n background: var(--sun);\n color: var(--ink-950);\n}\n\n.steps {\n display: grid;\n margin: 48px 0 0;\n padding: 0;\n counter-reset: step;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n list-style: none;\n}\n\n.step {\n position: relative;\n min-height: 180px;\n padding: 30px clamp(20px, 3vw, 34px);\n counter-increment: step;\n border-top: 1px solid var(--line);\n border-bottom: 1px solid var(--line);\n}\n\n.step + .step {\n border-left: 1px solid var(--line);\n}\n\n.step::before {\n content: \"0\" counter(step);\n display: block;\n margin-bottom: 30px;\n color: var(--coral);\n font: 750 13px/1 var(--mono);\n}\n\n.step h3 {\n margin: 0 0 8px;\n font-size: 19px;\n font-weight: 750;\n letter-spacing: -.02em;\n}\n\n.step p {\n margin: 0;\n color: var(--ink-600);\n font-size: 14px;\n}\n\n.inline-code,\n.rich-text code,\n.agent-table code,\n.feature-row code {\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--surface);\n color: var(--green-dark);\n padding: .12em .42em;\n font-family: var(--mono);\n font-size: .88em;\n}\n\n.feature-layout {\n display: grid;\n align-items: start;\n grid-template-columns: minmax(280px, 5fr) minmax(0, 7fr);\n gap: clamp(40px, 8vw, 100px);\n}\n\n.feature-spotlight {\n position: sticky;\n top: 104px;\n overflow: hidden;\n border: 1px solid var(--ink-950);\n border-radius: 24px;\n background: var(--sun);\n padding: clamp(28px, 4vw, 44px);\n}\n\n.feature-spotlight::after {\n content: \"ab\";\n position: absolute;\n right: 0;\n bottom: -58px;\n color: rgba(18, 37, 29, .09);\n font: 800 clamp(120px, 18vw, 220px)/1 var(--display);\n letter-spacing: -.08em;\n}\n\n.feature-spotlight .kicker {\n color: var(--ink-800);\n}\n\n.feature-spotlight h3 {\n max-width: 470px;\n margin: 0;\n font-size: clamp(30px, 4vw, 48px);\n font-weight: 800;\n letter-spacing: -.045em;\n line-height: 1.08;\n}\n\n.feature-spotlight p {\n position: relative;\n z-index: 1;\n max-width: 48ch;\n margin: 24px 0 0;\n color: var(--ink-800);\n}\n\n.feature-list {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.feature-row {\n display: grid;\n padding: 25px 0 28px;\n border-top: 1px solid var(--line);\n grid-template-columns: 44px 1fr;\n gap: 18px;\n}\n\n.feature-row:last-child {\n border-bottom: 1px solid var(--line);\n}\n\n.feature-index {\n color: var(--green);\n font: 750 12px/1.5 var(--mono);\n}\n\n.feature-row h3 {\n margin: -4px 0 7px;\n font-size: 20px;\n font-weight: 750;\n letter-spacing: -.025em;\n}\n\n.feature-row p {\n max-width: 60ch;\n margin: 0;\n color: var(--ink-600);\n font-size: 14px;\n}\n\n.agent-panel {\n overflow: hidden;\n border: 1px solid var(--ink-950);\n border-radius: 24px;\n background: var(--surface);\n}\n\n.agent-panel-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 20px;\n border-bottom: 1px solid var(--line);\n padding: 20px 24px;\n}\n\n.agent-count {\n display: flex;\n align-items: baseline;\n gap: 9px;\n}\n\n.agent-count strong {\n font: 800 38px/1 var(--display);\n letter-spacing: -.04em;\n}\n\n.agent-count span,\n.agent-legend {\n color: var(--ink-600);\n font-size: 13px;\n}\n\n.table-scroll {\n max-width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n}\n\n.agent-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 14px;\n}\n\n.agent-table th,\n.agent-table td {\n min-height: 52px;\n padding: 14px 20px;\n border-bottom: 1px solid var(--line);\n text-align: left;\n vertical-align: middle;\n}\n\n.agent-table th {\n background: var(--soft);\n color: var(--ink-600);\n font-size: 12px;\n font-weight: 750;\n letter-spacing: .04em;\n text-transform: uppercase;\n}\n\n.agent-table tr:last-child td {\n border-bottom: 0;\n}\n\n.agent-table tbody tr:hover {\n background: #fffdf3;\n}\n\n.agent-name {\n font-weight: 750;\n}\n\n.status {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n white-space: nowrap;\n}\n\n.status::before {\n content: \"\";\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--ink-400);\n}\n\n.status-ready {\n color: var(--success);\n font-weight: 650;\n}\n\n.status-ready::before {\n background: var(--success);\n}\n\n.agent-footnote {\n margin: 0;\n border-top: 1px solid var(--line);\n background: var(--sun-soft);\n color: var(--ink-800);\n padding: 16px 22px;\n font-size: 13px;\n}\n\n.detail-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 1px;\n border: 1px solid var(--line);\n background: var(--line);\n}\n\n.detail-block {\n min-height: 320px;\n background: var(--paper);\n padding: clamp(30px, 5vw, 58px);\n}\n\n.detail-block:nth-child(2) {\n background: var(--sky);\n}\n\n.detail-block h3 {\n max-width: 470px;\n margin: 0 0 20px;\n font-size: clamp(27px, 3vw, 38px);\n font-weight: 800;\n letter-spacing: -.04em;\n line-height: 1.1;\n}\n\n.rich-text {\n color: var(--ink-800);\n}\n\n.rich-text p {\n margin: 0 0 16px;\n}\n\n.rich-text ul {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.rich-text li {\n position: relative;\n padding: 11px 0 11px 24px;\n border-top: 1px solid rgba(18, 37, 29, .15);\n}\n\n.rich-text li::before {\n content: \"\";\n position: absolute;\n top: 20px;\n left: 2px;\n width: 8px;\n height: 8px;\n border-radius: 2px;\n background: var(--green);\n rotate: 45deg;\n}\n\n.docs-list {\n display: grid;\n margin: 0;\n padding: 0;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n list-style: none;\n}\n\n.docs-link {\n display: flex;\n min-height: 128px;\n flex-direction: column;\n justify-content: space-between;\n gap: 24px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: var(--surface);\n color: var(--ink-950);\n padding: 22px;\n text-decoration: none;\n transition: border-color 160ms var(--ease), translate 160ms var(--ease);\n}\n\n.docs-link:hover {\n border-color: var(--green);\n color: var(--green-dark);\n translate: 0 -3px;\n}\n\n.docs-link strong {\n font-size: 16px;\n}\n\n.docs-link span {\n color: var(--ink-600);\n font-size: 13px;\n}\n\n.text-link {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n font-weight: 750;\n}\n\n.final-call {\n position: relative;\n overflow: hidden;\n contain: paint;\n border: 1px solid var(--ink-950);\n border-radius: 28px;\n background: var(--green);\n color: #fff;\n padding: clamp(36px, 7vw, 76px);\n}\n\n.final-call::after {\n content: \"\";\n position: absolute;\n right: -80px;\n bottom: -120px;\n width: 320px;\n height: 320px;\n border: 58px solid var(--sun);\n border-radius: 50%;\n opacity: .98;\n}\n\n.final-call h2 {\n position: relative;\n z-index: 1;\n max-width: 700px;\n font-size: clamp(36px, 6vw, 66px);\n}\n\n.final-call p {\n position: relative;\n z-index: 1;\n max-width: 55ch;\n margin: 22px 0 0;\n color: #dff8e9;\n font-size: 17px;\n}\n\n.final-call .hero-actions {\n position: relative;\n z-index: 1;\n}\n\n.final-call .button-primary {\n border-color: var(--sun);\n background: var(--sun);\n color: var(--ink-950);\n}\n\n.final-call .button-primary:hover {\n border-color: #ffe277;\n background: #ffe277;\n color: var(--ink-950);\n}\n\n.final-call .button-secondary {\n border-color: rgba(255, 255, 255, .5);\n background: transparent;\n color: #fff;\n}\n\n.final-call .button-secondary:hover {\n border-color: #fff;\n background: rgba(255, 255, 255, .1);\n color: #fff;\n}\n\n.site-footer {\n padding-block: 34px 44px;\n}\n\n.footer-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 30px;\n}\n\n.footer-note {\n max-width: 680px;\n margin: 13px 0 0;\n color: var(--ink-600);\n font-size: 13px;\n}\n\n.footer-links {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 6px 18px;\n font-size: 13px;\n font-weight: 650;\n}\n\n.footer-links a {\n min-height: 44px;\n display: inline-flex;\n align-items: center;\n}\n\n.not-found {\n display: grid;\n min-height: 100svh;\n place-items: center;\n padding: 40px 24px;\n}\n\n.not-found-card {\n position: relative;\n width: min(720px, 100%);\n overflow: hidden;\n border: 1px solid var(--ink-950);\n border-radius: 28px;\n background: var(--surface);\n padding: clamp(32px, 8vw, 72px);\n box-shadow: 18px 18px 0 var(--sun);\n}\n\n.not-found-code {\n margin: 0 0 28px;\n color: var(--coral);\n font: 800 clamp(60px, 15vw, 132px)/.8 var(--display);\n letter-spacing: -.07em;\n}\n\n.not-found h1 {\n max-width: 620px;\n font-size: clamp(34px, 7vw, 58px);\n line-height: 1.06;\n}\n\n.not-found p {\n max-width: 50ch;\n margin: 22px 0 0;\n color: var(--ink-600);\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .hero-copy,\n .hero-actions,\n .hero-proof {\n animation: rise-in 620ms var(--ease) both;\n }\n\n .hero-actions {\n animation-delay: 100ms;\n }\n\n .hero-proof {\n animation-delay: 180ms;\n }\n\n .terminal {\n animation: terminal-in 720ms 80ms var(--ease) both;\n }\n\n .orbit-dot {\n animation: pulse 2.8s var(--ease) infinite;\n }\n\n @keyframes rise-in {\n from { opacity: 0; translate: 0 16px; }\n to { opacity: 1; translate: 0; }\n }\n\n @keyframes terminal-in {\n from { opacity: 0; translate: -46% -46%; }\n to { opacity: 1; translate: -50% -50%; }\n }\n\n @keyframes pulse {\n 50% { scale: 1.14; }\n }\n}\n\n@media (max-width: 900px) {\n .hero {\n min-height: auto;\n grid-template-columns: 1fr;\n padding-block: 54px 90px;\n }\n\n .launch-visual {\n min-height: 410px;\n }\n\n .section-heading,\n .install-layout,\n .feature-layout {\n grid-template-columns: 1fr;\n }\n\n .install-intro,\n .feature-spotlight {\n position: static;\n }\n\n .install-intro {\n display: grid;\n align-items: end;\n grid-template-columns: 1fr 1fr;\n gap: 30px;\n }\n\n .security-note {\n margin: 0;\n }\n\n .feature-spotlight {\n min-height: 310px;\n }\n}\n\n@media (max-width: 720px) {\n .shell {\n width: min(100% - 32px, 1180px);\n }\n\n .site-nav {\n min-height: 68px;\n }\n\n .nav-link {\n display: none;\n }\n\n .hero {\n gap: 42px;\n padding-top: 46px;\n }\n\n h1 {\n font-size: clamp(44px, 14vw, 68px);\n }\n\n .hero-copy {\n font-size: 17px;\n }\n\n .launch-visual {\n min-height: 350px;\n }\n\n .orbit-one {\n width: min(94%, 350px);\n }\n\n .orbit-two {\n width: min(72%, 270px);\n }\n\n .terminal {\n box-shadow: 11px 11px 0 var(--sun);\n }\n\n .terminal-body {\n min-height: 210px;\n padding: 22px 18px;\n }\n\n .section-heading {\n align-items: start;\n gap: 18px;\n margin-bottom: 38px;\n }\n\n .install-intro {\n display: block;\n }\n\n .security-note {\n margin-top: 24px;\n }\n\n .steps,\n .detail-grid,\n .docs-list {\n grid-template-columns: 1fr;\n }\n\n .step {\n min-height: auto;\n border-bottom: 0;\n padding: 24px 4px;\n }\n\n .step + .step {\n border-left: 0;\n }\n\n .step:last-child {\n border-bottom: 1px solid var(--line);\n }\n\n .step::before {\n margin-bottom: 16px;\n }\n\n .agent-panel-head,\n .footer-row {\n align-items: flex-start;\n flex-direction: column;\n }\n\n .agent-table th,\n .agent-table td {\n padding-inline: 14px;\n }\n\n .agent-table th:nth-child(4),\n .agent-table td:nth-child(4) {\n display: none;\n }\n\n .footer-links {\n justify-content: flex-start;\n }\n\n .final-call::after {\n right: -140px;\n bottom: -180px;\n }\n}\n\n@media (max-width: 480px) {\n .brand {\n font-size: 16px;\n }\n\n .brand-mark {\n width: 32px;\n height: 32px;\n }\n\n .language-link {\n padding-inline: 10px;\n }\n\n .hero-actions,\n .hero-actions .button {\n width: 100%;\n }\n\n .hero-proof {\n display: grid;\n grid-template-columns: 1fr 1fr;\n }\n\n .launch-visual {\n min-height: 320px;\n }\n\n .terminal {\n border-radius: 17px;\n }\n\n .terminal-bar {\n min-height: 42px;\n }\n\n .terminal-body {\n min-height: 190px;\n font-size: 11px;\n }\n\n .command-head {\n padding-left: 14px;\n }\n\n .copy-button {\n min-width: 82px;\n }\n\n .command-code {\n padding: 19px 14px;\n font-size: 11px;\n }\n\n .feature-spotlight {\n min-height: 330px;\n }\n\n .feature-row {\n grid-template-columns: 34px 1fr;\n gap: 10px;\n }\n\n .agent-table th:first-child,\n .agent-table td:first-child,\n .agent-table th:nth-child(4),\n .agent-table td:nth-child(4) {\n display: none;\n }\n\n .agent-table {\n font-size: 12px;\n }\n\n .agent-table th,\n .agent-table td {\n padding-inline: 10px;\n }\n\n .agent-legend {\n display: none;\n }\n\n .not-found-card {\n box-shadow: 10px 10px 0 var(--sun);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n html {\n scroll-behavior: auto;\n }\n\n *,\n *::before,\n *::after {\n animation-duration: .01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: .01ms !important;\n }\n}\n\n@media print {\n .site-nav,\n .hero-actions,\n .copy-button,\n .final-call,\n .site-footer {\n display: none;\n }\n\n body {\n background: #fff;\n }\n\n .section,\n .hero {\n min-height: auto;\n padding-block: 28px;\n }\n}\n","type":"text/css; charset=utf-8","etag":"\"93e52ab55ce71853\""},"/assets/site.js":{"body":"(function () {\n \"use strict\";\n\n var copyButtons = document.querySelectorAll(\"[data-copy]\");\n\n function legacyCopy(text) {\n var input = document.createElement(\"textarea\");\n input.value = text;\n input.setAttribute(\"readonly\", \"\");\n input.style.position = \"fixed\";\n input.style.opacity = \"0\";\n document.body.appendChild(input);\n input.select();\n var copied = document.execCommand(\"copy\");\n input.remove();\n return copied ? Promise.resolve() : Promise.reject(new Error(\"copy failed\"));\n }\n\n function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) {\n return navigator.clipboard.writeText(text);\n }\n return legacyCopy(text);\n }\n\n copyButtons.forEach(function (button) {\n var idleLabel = button.getAttribute(\"data-label-idle\") || \"Copy\";\n var successLabel = button.getAttribute(\"data-label-success\") || \"Copied\";\n var errorLabel = button.getAttribute(\"data-label-error\") || \"Select command\";\n var label = button.querySelector(\"[data-copy-label]\");\n var timer;\n\n button.addEventListener(\"click\", function () {\n window.clearTimeout(timer);\n copyText(button.getAttribute(\"data-copy\") || \"\").then(function () {\n button.setAttribute(\"data-state\", \"success\");\n label.textContent = successLabel;\n }).catch(function () {\n button.setAttribute(\"data-state\", \"error\");\n label.textContent = errorLabel;\n }).finally(function () {\n timer = window.setTimeout(function () {\n button.removeAttribute(\"data-state\");\n label.textContent = idleLabel;\n }, 1800);\n });\n });\n });\n}());\n","type":"text/javascript; charset=utf-8","etag":"\"e32ad7eff5bfef9a\""},"/assets/favicon.svg":{"body":"\n \n \n\n","type":"image/svg+xml; charset=utf-8","etag":"\"97f7d8aaef171778\""}}); +export const WEB_ASSETS = Object.freeze({"/index.html":{"body":"\n\n\n \n \n \n \n AgentBoot · 一条命令,启动你的 AI Agent\n \n \n \n \n \n \n \n\n\n 跳到主要内容\n\n \n\n
\n
\n

v1.3.0 · 免费开源 · 三平台可用

\n

一条命令,
Agent 就位

\n

把安装、模型、镜像、离线包和安全卸载收进一个清楚的菜单。你选择要用的 AI Agent,AgentBoot 负责把它可靠地装好。

\n \n
    \n
  • 15 个 Agent 按需选择
  • \n
  • Linux · macOS · Windows
  • \n
  • 离线安装
  • \n
  • MIT License
  • \n
\n
\n\n
\n \n \n \n
\n
\n \n agentboot\n
\n
\n

$ agentboot

\n

检查系统环境与网络…

\n

选择你想安装的 Agent

\n

校验来源与 SHA-256

\n

安装完成,开始创造。

\n
\n
\n
\n
\n\n
\n
\n
\n
\n
\n

从这里开始

\n

复制一行,马上启动

\n

安装完成后输入 agentboot 打开控制台菜单,或输入 ab 直接与内置 Agent 对话。

\n
\n
\n \n \n \n

安装器仅使用项目控制的 Worker、GitHub Pages 与 GitHub Release,并对安装包执行同源 SHA-256 校验。

\n
\n
\n\n
\n
\n
\n Linux / macOS\n \n
\n curl -fsSL https://boot.ide.pub/install.sh | sh\n
\n\n
\n
\n Windows PowerShell\n \n
\n powershell -NoProfile -ExecutionPolicy Bypass -Command \"iex ((New-Object Net.WebClient).DownloadString('https://boot.ide.pub/install.ps1'))\"\n
\n
\n
\n\n
    \n
  1. 安装 AgentBoot

    上面一条命令约 10 秒完成,只安装 AgentBoot 本体与内置 Agent。

  2. \n
  3. 选择需要的 Agent

    运行 agentboot,在菜单中安装、卸载和配置。

  4. \n
  5. 开始工作

    运行选好的 Agent;也可以直接输入 ab 零配置对话。

  6. \n
\n
\n\n
\n
\n

不仅是安装脚本

复杂留在里面,选择留给你

\n

不塞给你一个全家桶,也不把失败留给你排查。AgentBoot 把最容易出错的环境、网络、来源与卸载边界统一处理。

\n
\n\n
\n
\n

始终可用的起点

\n

即使其他 Agent 装不上,ab 也能马上工作

\n

零第三方依赖 Python 核心,默认接入 Agnes 免费模型,并内置离线 Linux 知识库、会话持久化和高危命令拦截。

\n
\n\n
    \n
  1. 01

    按需安装,不装全家桶

    15 个主流 Agent 自由选择:Cursor、Claude Code、Codex、Qwen Code、OpenCode、MiMo、Cline、Pi、CoCo 等。

  2. \n
  3. 02

    模型提供商统一管理

    Agnes 零配置可用,也支持命名管理任意 OpenAI 兼容接口、Ollama、LM Studio 与故障切换顺序。

  4. \n
  5. 03

    中国网络自动适配

    自动选择 npm、Node、PyPI 镜像,Worker、Pages、Release 三源容错,并提供一键代理配置。

  6. \n
  7. 04

    离线包经过真实验证

    发布包完成安装、启动、卸载冒烟;菜单 [7] 还能在目标平台按需构建自定义包。

  8. \n
  9. 05

    卸载知道自己该删什么

    菜单 [9]uninstall 只清理由 AgentBoot 管理的程序,默认保留配置、认证和会话。

  10. \n
  11. 06

    性能可以现场测量

    TLS 连接复用、知识库预建索引、上下文自动瘦身和流式中断保护;/bench 基于当前网络与模型实测。

  12. \n
\n
\n
\n\n
\n
\n
\n

你的工具箱

15 个 Agent,一个入口

\n

在线安装按需选择;标记为离线可用的 Agent 可连同依赖与运行时一起打包。

\n
\n\n
\n
\n
15个内置选项
\n 绿点表示支持离线安装\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
#Agent命令厂商离线
01CoCo AgentcocoBitCookLinux/macOS
02OpenCodeopencodeopencode.ai支持
03Hermes AgenthermesHermes需 Git
04Cline CLIclineCline支持
05CodeBuddy CLIcodebuddyTencent支持
06Pi Coding AgentpiEarendil Works支持
07Claude CodeclaudeAnthropic支持
08OpenAI Codex CLIcodexOpenAIAgnes 预置
09Qwen CodeqwenAlibabaAgnes 预置
10MiMo CodemimoXiaomi支持
11OpenClawopenclawOpenClaw支持
12Gemini CLIgeminiGoogle支持
13iFlow CLIiflowiFlow 心流支持
14AideraiderAider AI仅在线
15CursorcursorAnysphere仅在线
\n
\n

Codex 与 Qwen 安装后自动预置 Agnes 免费模型;也可通过菜单 +add-agent 添加自己的 Agent。

\n
\n
\n
\n\n
\n
\n
\n

没有网络,也能安装

\n

可验证离线包,把完整运行环境一起带走

\n

v1.3.0 提供 Codex 与 OpenCode 精简包;CI 在 Linux、Windows、Intel macOS 与 Apple Silicon 上实际验证安装、版本启动与卸载。

\n
    \n
  • 已验证精简包:从 Releases 下载后直接带到目标机。
  • \n
  • 自建离线包:用菜单 [7]build-offline 在目标平台按需构建。
  • \n
  • 无需额外解压软件:运行包内 install-offline.ps1sh install-offline.sh
  • \n
\n
\n\n
\n

模型由你决定

\n

开箱即用,也完全允许自定义

\n
    \n
  • Agnes 免费模型:ab 零配置直接用,Codex 与 Qwen 自动接线。
  • \n
  • 自定义提供商:任意 OpenAI 兼容接口,命名管理、随时切换。
  • \n
  • 本地模型:内置 Ollama 与 LM Studio 预设,也可添加 vLLM。
  • \n
  • 故障切换:设置备用顺序,主模型失败后自动降级。
  • \n
\n
\n
\n
\n\n
\n
\n
\n

需要更多细节

文档直接说人话

\n

从安装、离线部署到自定义 Agent,按你当前要解决的问题继续阅读。

\n
\n \n
\n
\n\n
\n
\n

少折腾环境,早点开始创造

\n

AgentBoot 是 MIT 许可的开源项目。先用一条命令开始,所有选择依然掌握在你手里。

\n \n
\n
\n
\n\n \n\n\n","type":"text/html; charset=utf-8","etag":"\"aef744c4f1ae842d\""},"/en/index.html":{"body":"\n\n\n \n \n \n \n AgentBoot · Launch your AI agents with one command\n \n \n \n \n \n \n \n\n\n Skip to main content\n\n \n\n
\n
\n

v1.3.0 · Free and open source · Three platforms

\n

One command.
Agents ready.

\n

Bring installation, models, mirrors, offline packs, and safe uninstall into one clear menu. You choose the AI agents you need; AgentBoot installs them reliably.

\n \n
    \n
  • Choose from 15 agents
  • \n
  • Linux · macOS · Windows
  • \n
  • Offline installation
  • \n
  • MIT License
  • \n
\n
\n\n
\n \n \n \n
\n
\n \n agentboot\n
\n
\n

$ agentboot

\n

Checking system and network…

\n

Choose the agents you want to install

\n

Verify source and SHA-256

\n

Ready. Start building.

\n
\n
\n
\n
\n\n
\n
\n
\n
\n
\n

Start here

\n

Copy one line. Start right away.

\n

After installation, run agentboot to open the console menu, or ab to chat directly with the built-in agent.

\n
\n
\n \n \n \n

The installer uses only project-controlled Worker, GitHub Pages, and GitHub Release sources, then verifies each installation package against a same-origin SHA-256 checksum.

\n
\n
\n\n
\n
\n
\n Linux / macOS\n \n
\n curl -fsSL https://boot.ide.pub/install.sh | sh\n
\n\n
\n
\n Windows PowerShell\n \n
\n powershell -NoProfile -ExecutionPolicy Bypass -Command \"iex ((New-Object Net.WebClient).DownloadString('https://boot.ide.pub/install.ps1'))\"\n
\n
\n
\n\n
    \n
  1. Install AgentBoot

    The command above takes about 10 seconds and installs only AgentBoot itself and the built-in agent.

  2. \n
  3. Choose the agents you need

    Run agentboot to install, uninstall, and configure agents from the menu.

  4. \n
  5. Get to work

    Launch your chosen agent, or run ab for a zero-configuration chat.

  6. \n
\n
\n\n
\n
\n

More than an install script

AgentBoot handles the complexity. You make the choices.

\n

No all-in-one bundle, and no failed setup left for you to debug. AgentBoot handles the parts most likely to break: environments, networks, sources, and uninstall boundaries.

\n
\n\n
\n
\n

A starting point that's always ready

\n

Even if other agents won't install, ab works right away

\n

The built-in agent uses a Python core with no third-party dependencies, the free Agnes model by default, an offline Linux knowledge base, persistent sessions, and safeguards against high-risk commands.

\n
\n\n
    \n
  1. 01

    Install on demand, not as a bundle

    Choose from 15 mainstream agents, including Cursor, Claude Code, Codex, Qwen Code, OpenCode, MiMo, Cline, Pi, and CoCo.

  2. \n
  3. 02

    Manage model providers in one place

    Agnes works with zero configuration. You can also name and manage any OpenAI-compatible endpoint, Ollama, or LM Studio, and set a failover order.

  4. \n
  5. 03

    Automatically adapt to networks in China

    AgentBoot selects npm, Node, and PyPI mirrors automatically, fails over across Worker, Pages, and Release sources, and configures a proxy in one step.

  6. \n
  7. 04

    Offline packs are tested end to end

    Release packs pass install, launch, and uninstall smoke tests; menu [7] can also build a custom pack on demand on the target platform.

  8. \n
  9. 05

    Uninstall only what AgentBoot owns

    Menu [9] or uninstall removes AgentBoot-managed programs while keeping configuration, credentials, and sessions by default.

  10. \n
  11. 06

    Measure performance where it matters

    AgentBoot reuses TLS connections, prebuilds knowledge-base indexes, trims context automatically, and protects interrupted streams. /bench measures your current network and model.

  12. \n
\n
\n
\n\n
\n
\n
\n

Your toolbox

15 agents. One front door.

\n

Choose agents as needed for online installation. Agents marked as offline-ready can be packaged with their dependencies and runtimes.

\n
\n\n
\n
\n
15built-in choices
\n A green dot indicates support for offline installation\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
#AgentCommandVendorOffline
01CoCo AgentcocoBitCookLinux/macOS
02OpenCodeopencodeopencode.aiReady
03Hermes AgenthermesHermesRequires Git
04Cline CLIclineClineSupported
05CodeBuddy CLIcodebuddyTencentSupported
06Pi Coding AgentpiEarendil WorksSupported
07Claude CodeclaudeAnthropicSupported
08OpenAI Codex CLIcodexOpenAIAgnes preset
09Qwen CodeqwenAlibabaAgnes preset
10MiMo CodemimoXiaomiSupported
11OpenClawopenclawOpenClawSupported
12Gemini CLIgeminiGoogleSupported
13iFlow CLIiflowiFlowSupported
14AideraiderAider AIOnline only
15CursorcursorAnysphereOnline only
\n
\n

Codex and Qwen are automatically configured with the free Agnes model after installation. Add your own agent with menu + or the add-agent command.

\n
\n
\n
\n\n
\n
\n
\n

Install without internet access

\n

Verified offline packs bring the complete runtime with you

\n

v1.3.0 provides slim Codex and OpenCode packs. CI verifies install, version launch, and uninstall on Linux, Windows, Intel macOS, and Apple Silicon.

\n
    \n
  • Verified slim packs: download from Releases and carry them to the target machine.
  • \n
  • Custom offline packs: use menu [7] or build-offline on the target platform.
  • \n
  • No extra extraction software: run install-offline.ps1 or sh install-offline.sh inside the pack.
  • \n
\n
\n\n
\n

Your models, your choice

\n

Ready out of the box and fully customizable

\n
    \n
  • Agnes free model: use ab with zero setup; Codex and Qwen are configured automatically.
  • \n
  • Custom providers: name, manage, and switch any OpenAI-compatible endpoint.
  • \n
  • Local models: Ollama and LM Studio presets, with vLLM available as a custom provider.
  • \n
  • Failover: set a fallback order and switch automatically when the primary fails.
  • \n
\n
\n
\n
\n\n
\n
\n
\n

More detail when you need it

Documentation that speaks plainly

\n

Start with the problem in front of you, from installation and offline deployment to custom agents.

\n
\n \n
\n
\n\n
\n
\n

Spend less time wrestling with setup. Start building sooner.

\n

AgentBoot is open source under the MIT License. Start with one command; every choice stays in your hands.

\n \n
\n
\n
\n\n \n\n\n","type":"text/html; charset=utf-8","etag":"\"3d6b47b17bad4169\""},"/404.html":{"body":"\n\n\n \n \n \n \n 页面未找到 · AgentBoot\n \n \n\n\n
\n
\n

404

\n

这条路径没有 Agent,回到启动台吧

\n

地址可能已变更,或只是多输入了一个字符。项目主页、安装命令和文档都还在。

\n \n
\n
\n\n\n","type":"text/html; charset=utf-8","etag":"\"721415dd4c101c10\""},"/assets/site.css":{"body":":root {\n color-scheme: light;\n --ink-950: #12251d;\n --ink-800: #294238;\n --ink-600: #5b7067;\n --ink-400: #91a198;\n --paper: #fffdf5;\n --surface: #ffffff;\n --soft: #f2f7f2;\n --line: #dce7df;\n --green: #137a52;\n --green-dark: #0d5e3e;\n --green-soft: #e3f5eb;\n --sun: #ffd84d;\n --sun-soft: #fff3b9;\n --coral: #ff7a59;\n --sky: #dff3ff;\n --success: #137a52;\n --warning: #a65e00;\n --error: #b42318;\n --info: #176b87;\n --code: #10251d;\n --code-text: #ecfff4;\n --display: ui-rounded, \"Avenir Next Rounded\", \"SF Pro Rounded\", \"Segoe UI\", sans-serif;\n --body: Inter, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif;\n --mono: \"SFMono-Regular\", Consolas, \"Liberation Mono\", monospace;\n --ease: cubic-bezier(.2, .8, .2, 1);\n}\n\n* {\n box-sizing: border-box;\n}\n\nhtml {\n overflow-x: clip;\n scroll-behavior: smooth;\n scroll-padding-top: 88px;\n}\n\nbody {\n margin: 0;\n background: var(--paper);\n color: var(--ink-950);\n font-family: var(--body);\n font-size: 16px;\n line-height: 1.68;\n text-rendering: optimizeLegibility;\n -webkit-font-smoothing: antialiased;\n}\n\nbutton,\na {\n -webkit-tap-highlight-color: transparent;\n}\n\na {\n color: var(--green-dark);\n text-decoration-thickness: 1.5px;\n text-underline-offset: .2em;\n}\n\na:hover {\n color: var(--green);\n}\n\n:focus-visible {\n outline: 3px solid var(--sun);\n outline-offset: 3px;\n}\n\n.skip-link {\n position: fixed;\n z-index: 100;\n top: 12px;\n left: 12px;\n translate: 0 -150%;\n border: 1px solid var(--ink-950);\n border-radius: 10px;\n background: var(--surface);\n color: var(--ink-950);\n padding: 10px 14px;\n font-weight: 700;\n}\n\n.skip-link:focus {\n translate: 0;\n}\n\n.shell {\n width: min(1180px, calc(100% - 48px));\n margin-inline: auto;\n}\n\n.site-nav {\n position: relative;\n z-index: 10;\n display: flex;\n min-height: 76px;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n}\n\n.brand {\n display: inline-flex;\n min-height: 44px;\n align-items: center;\n gap: 11px;\n color: var(--ink-950);\n font-family: var(--display);\n font-size: 18px;\n font-weight: 800;\n letter-spacing: -.025em;\n text-decoration: none;\n}\n\n.brand-mark {\n position: relative;\n display: inline-grid;\n width: 34px;\n height: 34px;\n place-items: center;\n overflow: hidden;\n border: 2px solid var(--ink-950);\n border-radius: 11px;\n background: var(--sun);\n}\n\n.brand-mark::before,\n.brand-mark::after {\n content: \"\";\n position: absolute;\n height: 2px;\n border-radius: 999px;\n background: var(--ink-950);\n transform-origin: left center;\n}\n\n.brand-mark::before {\n width: 12px;\n translate: -1px -3px;\n rotate: 38deg;\n}\n\n.brand-mark::after {\n width: 10px;\n translate: 2px 5px;\n}\n\n.nav-links {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.nav-link,\n.language-link {\n display: inline-flex;\n min-height: 44px;\n align-items: center;\n justify-content: center;\n border-radius: 10px;\n color: var(--ink-800);\n padding: 8px 11px;\n font-size: 14px;\n font-weight: 650;\n text-decoration: none;\n}\n\n.nav-link:hover,\n.language-link:hover {\n background: var(--green-soft);\n color: var(--green-dark);\n}\n\n.language-link {\n border: 1px solid var(--line);\n background: rgba(255, 255, 255, .72);\n}\n\n.hero {\n position: relative;\n display: grid;\n min-height: 660px;\n align-items: center;\n grid-template-columns: minmax(0, 7fr) minmax(340px, 5fr);\n gap: clamp(32px, 6vw, 86px);\n padding-block: 72px 112px;\n}\n\n.hero::before {\n content: \"\";\n position: absolute;\n z-index: -2;\n top: 2%;\n right: 0;\n width: min(48vw, 620px);\n aspect-ratio: 1;\n border-radius: 50%;\n background: var(--sun-soft);\n}\n\n.hero::after {\n content: \"\";\n position: absolute;\n z-index: -1;\n bottom: 9%;\n left: 0;\n width: min(30vw, 380px);\n aspect-ratio: 1;\n border-radius: 50%;\n background: var(--sky);\n opacity: .7;\n}\n\n.eyebrow {\n display: inline-flex;\n align-items: center;\n gap: 9px;\n margin: 0 0 22px;\n color: var(--green-dark);\n font-size: 13px;\n font-weight: 750;\n letter-spacing: .08em;\n text-transform: uppercase;\n}\n\n.eyebrow::before {\n content: \"\";\n width: 9px;\n height: 9px;\n border-radius: 50%;\n background: var(--coral);\n box-shadow: 0 0 0 5px rgba(255, 122, 89, .14);\n}\n\nh1,\nh2,\nh3 {\n font-family: var(--display);\n text-wrap: balance;\n}\n\nh1 {\n max-width: 800px;\n margin: 0;\n font-size: clamp(48px, 7.1vw, 88px);\n font-weight: 800;\n letter-spacing: -.06em;\n line-height: .98;\n}\n\n.hero-highlight {\n position: relative;\n display: inline-block;\n white-space: nowrap;\n}\n\n.hero-highlight::after {\n content: \"\";\n position: absolute;\n z-index: -1;\n right: -.08em;\n bottom: .04em;\n left: -.02em;\n height: .24em;\n border-radius: 99px;\n background: var(--sun);\n rotate: -1.5deg;\n}\n\n.hero-copy {\n max-width: 650px;\n margin: 28px 0 0;\n color: var(--ink-600);\n font-size: clamp(17px, 2vw, 21px);\n line-height: 1.62;\n}\n\n.hero-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n margin-top: 34px;\n}\n\n.button {\n display: inline-flex;\n min-height: 48px;\n align-items: center;\n justify-content: center;\n gap: 9px;\n border: 1px solid var(--ink-950);\n border-radius: 12px;\n padding: 11px 17px;\n font-size: 15px;\n font-weight: 750;\n line-height: 1;\n text-decoration: none;\n transition: translate 160ms var(--ease), background-color 160ms var(--ease), color 160ms var(--ease);\n}\n\n.button-primary {\n background: var(--green);\n color: #fff;\n border-color: var(--green);\n}\n\n.button-primary:hover {\n background: var(--green-dark);\n color: #fff;\n translate: 0 -2px;\n}\n\n.button-secondary {\n background: var(--surface);\n color: var(--ink-950);\n}\n\n.button-secondary:hover {\n background: var(--sun-soft);\n color: var(--ink-950);\n translate: 0 -2px;\n}\n\n.button svg,\n.text-link svg {\n width: 18px;\n height: 18px;\n flex: 0 0 auto;\n fill: none;\n stroke: currentColor;\n stroke-linecap: round;\n stroke-linejoin: round;\n stroke-width: 1.8;\n}\n\n.hero-proof {\n display: flex;\n flex-wrap: wrap;\n gap: 10px 18px;\n margin: 26px 0 0;\n padding: 0;\n color: var(--ink-600);\n font-size: 13px;\n font-weight: 650;\n list-style: none;\n}\n\n.hero-proof li {\n display: flex;\n align-items: center;\n gap: 7px;\n}\n\n.hero-proof li::before {\n content: \"\";\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--green);\n}\n\n.launch-visual {\n position: relative;\n min-height: 450px;\n}\n\n.orbit {\n position: absolute;\n inset: 0;\n margin: auto;\n border: 1px solid rgba(18, 37, 29, .18);\n border-radius: 50%;\n}\n\n.orbit-one {\n width: min(100%, 430px);\n aspect-ratio: 1;\n}\n\n.orbit-two {\n width: min(76%, 330px);\n aspect-ratio: 1;\n border-style: dashed;\n}\n\n.orbit-dot {\n position: absolute;\n top: 10%;\n right: 14%;\n width: 18px;\n height: 18px;\n border: 3px solid var(--paper);\n border-radius: 50%;\n background: var(--coral);\n box-shadow: 0 0 0 1px var(--ink-950);\n}\n\n.terminal {\n position: absolute;\n inset: 50% auto auto 50%;\n width: min(94%, 390px);\n overflow: hidden;\n border: 2px solid var(--ink-950);\n border-radius: 22px;\n background: var(--code);\n box-shadow: 18px 18px 0 var(--sun);\n color: var(--code-text);\n translate: -50% -50%;\n}\n\n.terminal-bar {\n display: flex;\n min-height: 48px;\n align-items: center;\n justify-content: space-between;\n border-bottom: 1px solid rgba(255, 255, 255, .13);\n padding: 0 17px;\n color: #b9d3c4;\n font-family: var(--mono);\n font-size: 12px;\n}\n\n.terminal-dots {\n display: flex;\n gap: 6px;\n}\n\n.terminal-dots i {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--coral);\n}\n\n.terminal-dots i:nth-child(2) {\n background: var(--sun);\n}\n\n.terminal-dots i:nth-child(3) {\n background: #57d49b;\n}\n\n.terminal-body {\n min-height: 238px;\n padding: 26px 22px;\n font-family: var(--mono);\n font-size: clamp(12px, 1.35vw, 14px);\n line-height: 1.8;\n}\n\n.terminal-body p {\n margin: 0 0 12px;\n}\n\n.prompt {\n color: #69e4a7;\n}\n\n.terminal-muted {\n color: #9bb7a8;\n}\n\n.terminal-check {\n color: var(--sun);\n}\n\n.section {\n padding-block: clamp(72px, 10vw, 120px);\n}\n\n.section-soft {\n position: relative;\n background: var(--soft);\n}\n\n.section-soft::before,\n.section-soft::after {\n content: \"\";\n position: absolute;\n right: 0;\n left: 0;\n height: 1px;\n background: var(--line);\n}\n\n.section-soft::before {\n top: 0;\n}\n\n.section-soft::after {\n bottom: 0;\n}\n\n.section-heading {\n display: grid;\n align-items: end;\n grid-template-columns: minmax(0, 4fr) minmax(280px, 3fr);\n gap: 32px;\n margin-bottom: 54px;\n}\n\n.kicker {\n margin: 0 0 12px;\n color: var(--green);\n font-size: 13px;\n font-weight: 750;\n letter-spacing: .08em;\n text-transform: uppercase;\n}\n\nh2 {\n max-width: 700px;\n margin: 0;\n font-size: clamp(34px, 4.8vw, 56px);\n font-weight: 800;\n letter-spacing: -.045em;\n line-height: 1.06;\n}\n\n.section-lede {\n max-width: 56ch;\n margin: 0;\n color: var(--ink-600);\n font-size: 17px;\n}\n\n.install-layout {\n display: grid;\n align-items: start;\n grid-template-columns: minmax(240px, 4fr) minmax(0, 8fr);\n gap: clamp(34px, 7vw, 88px);\n}\n\n.install-intro {\n position: sticky;\n top: 104px;\n}\n\n.install-intro h2 {\n font-size: clamp(34px, 4vw, 48px);\n}\n\n.install-intro p {\n color: var(--ink-600);\n}\n\n.security-note {\n display: grid;\n grid-template-columns: 38px 1fr;\n gap: 13px;\n margin-top: 28px;\n padding-top: 22px;\n border-top: 1px solid var(--line);\n color: var(--ink-600);\n font-size: 14px;\n}\n\n.security-icon {\n display: grid;\n width: 38px;\n height: 38px;\n place-items: center;\n border-radius: 12px;\n background: var(--green-soft);\n color: var(--green-dark);\n}\n\n.security-icon svg {\n width: 21px;\n height: 21px;\n fill: none;\n stroke: currentColor;\n stroke-linecap: round;\n stroke-linejoin: round;\n stroke-width: 1.8;\n}\n\n.security-note p {\n margin: 0;\n}\n\n.command-stack {\n display: grid;\n gap: 16px;\n}\n\n.command-card {\n overflow: hidden;\n border: 1px solid var(--ink-950);\n border-radius: 20px;\n background: var(--surface);\n}\n\n.command-head {\n display: flex;\n min-height: 56px;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-bottom: 1px solid var(--line);\n padding: 10px 12px 10px 20px;\n}\n\n.command-platform {\n display: flex;\n align-items: center;\n gap: 11px;\n font-weight: 750;\n}\n\n.platform-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: var(--coral);\n}\n\n.command-card:nth-child(2) .platform-dot {\n background: #2f80ed;\n}\n\n.copy-button {\n display: inline-flex;\n min-width: 90px;\n min-height: 44px;\n align-items: center;\n justify-content: center;\n gap: 7px;\n border: 1px solid var(--line);\n border-radius: 10px;\n background: var(--soft);\n color: var(--ink-950);\n padding: 8px 12px;\n font: 700 13px/1 var(--body);\n cursor: pointer;\n transition: background-color 160ms var(--ease), border-color 160ms var(--ease);\n}\n\n.copy-button:hover {\n border-color: var(--green);\n background: var(--green-soft);\n}\n\n.copy-button[data-state=\"success\"] {\n border-color: var(--green);\n background: var(--green-soft);\n color: var(--green-dark);\n}\n\n.copy-button[data-state=\"error\"] {\n border-color: var(--error);\n background: #fff0ed;\n color: var(--error);\n}\n\n.copy-button svg {\n width: 16px;\n height: 16px;\n fill: none;\n stroke: currentColor;\n stroke-linecap: round;\n stroke-linejoin: round;\n stroke-width: 1.8;\n}\n\n.command-code {\n display: block;\n margin: 0;\n overflow-x: auto;\n background: var(--code);\n color: var(--code-text);\n padding: 24px 20px;\n font-family: var(--mono);\n font-size: 13px;\n line-height: 1.75;\n white-space: pre;\n scrollbar-color: #557266 var(--code);\n}\n\n.command-code::selection,\n.terminal ::selection {\n background: var(--sun);\n color: var(--ink-950);\n}\n\n.steps {\n display: grid;\n margin: 48px 0 0;\n padding: 0;\n counter-reset: step;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n list-style: none;\n}\n\n.step {\n position: relative;\n min-height: 180px;\n padding: 30px clamp(20px, 3vw, 34px);\n counter-increment: step;\n border-top: 1px solid var(--line);\n border-bottom: 1px solid var(--line);\n}\n\n.step + .step {\n border-left: 1px solid var(--line);\n}\n\n.step::before {\n content: \"0\" counter(step);\n display: block;\n margin-bottom: 30px;\n color: var(--coral);\n font: 750 13px/1 var(--mono);\n}\n\n.step h3 {\n margin: 0 0 8px;\n font-size: 19px;\n font-weight: 750;\n letter-spacing: -.02em;\n}\n\n.step p {\n margin: 0;\n color: var(--ink-600);\n font-size: 14px;\n}\n\n.inline-code,\n.rich-text code,\n.agent-table code,\n.feature-row code {\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--surface);\n color: var(--green-dark);\n padding: .12em .42em;\n font-family: var(--mono);\n font-size: .88em;\n}\n\n.feature-layout {\n display: grid;\n align-items: start;\n grid-template-columns: minmax(280px, 5fr) minmax(0, 7fr);\n gap: clamp(40px, 8vw, 100px);\n}\n\n.feature-spotlight {\n position: sticky;\n top: 104px;\n overflow: hidden;\n border: 1px solid var(--ink-950);\n border-radius: 24px;\n background: var(--sun);\n padding: clamp(28px, 4vw, 44px);\n}\n\n.feature-spotlight::after {\n content: \"ab\";\n position: absolute;\n right: 0;\n bottom: -58px;\n color: rgba(18, 37, 29, .09);\n font: 800 clamp(120px, 18vw, 220px)/1 var(--display);\n letter-spacing: -.08em;\n}\n\n.feature-spotlight .kicker {\n color: var(--ink-800);\n}\n\n.feature-spotlight h3 {\n max-width: 470px;\n margin: 0;\n font-size: clamp(30px, 4vw, 48px);\n font-weight: 800;\n letter-spacing: -.045em;\n line-height: 1.08;\n}\n\n.feature-spotlight p {\n position: relative;\n z-index: 1;\n max-width: 48ch;\n margin: 24px 0 0;\n color: var(--ink-800);\n}\n\n.feature-list {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.feature-row {\n display: grid;\n padding: 25px 0 28px;\n border-top: 1px solid var(--line);\n grid-template-columns: 44px 1fr;\n gap: 18px;\n}\n\n.feature-row:last-child {\n border-bottom: 1px solid var(--line);\n}\n\n.feature-index {\n color: var(--green);\n font: 750 12px/1.5 var(--mono);\n}\n\n.feature-row h3 {\n margin: -4px 0 7px;\n font-size: 20px;\n font-weight: 750;\n letter-spacing: -.025em;\n}\n\n.feature-row p {\n max-width: 60ch;\n margin: 0;\n color: var(--ink-600);\n font-size: 14px;\n}\n\n.agent-panel {\n overflow: hidden;\n border: 1px solid var(--ink-950);\n border-radius: 24px;\n background: var(--surface);\n}\n\n.agent-panel-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 20px;\n border-bottom: 1px solid var(--line);\n padding: 20px 24px;\n}\n\n.agent-count {\n display: flex;\n align-items: baseline;\n gap: 9px;\n}\n\n.agent-count strong {\n font: 800 38px/1 var(--display);\n letter-spacing: -.04em;\n}\n\n.agent-count span,\n.agent-legend {\n color: var(--ink-600);\n font-size: 13px;\n}\n\n.table-scroll {\n max-width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n}\n\n.agent-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 14px;\n}\n\n.agent-table th,\n.agent-table td {\n min-height: 52px;\n padding: 14px 20px;\n border-bottom: 1px solid var(--line);\n text-align: left;\n vertical-align: middle;\n}\n\n.agent-table th {\n background: var(--soft);\n color: var(--ink-600);\n font-size: 12px;\n font-weight: 750;\n letter-spacing: .04em;\n text-transform: uppercase;\n}\n\n.agent-table tr:last-child td {\n border-bottom: 0;\n}\n\n.agent-table tbody tr:hover {\n background: #fffdf3;\n}\n\n.agent-name {\n font-weight: 750;\n}\n\n.status {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n white-space: nowrap;\n}\n\n.status::before {\n content: \"\";\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--ink-400);\n}\n\n.status-ready {\n color: var(--success);\n font-weight: 650;\n}\n\n.status-ready::before {\n background: var(--success);\n}\n\n.agent-footnote {\n margin: 0;\n border-top: 1px solid var(--line);\n background: var(--sun-soft);\n color: var(--ink-800);\n padding: 16px 22px;\n font-size: 13px;\n}\n\n.detail-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 1px;\n border: 1px solid var(--line);\n background: var(--line);\n}\n\n.detail-block {\n min-height: 320px;\n background: var(--paper);\n padding: clamp(30px, 5vw, 58px);\n}\n\n.detail-block:nth-child(2) {\n background: var(--sky);\n}\n\n.detail-block h3 {\n max-width: 470px;\n margin: 0 0 20px;\n font-size: clamp(27px, 3vw, 38px);\n font-weight: 800;\n letter-spacing: -.04em;\n line-height: 1.1;\n}\n\n.rich-text {\n color: var(--ink-800);\n}\n\n.rich-text p {\n margin: 0 0 16px;\n}\n\n.rich-text ul {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.rich-text li {\n position: relative;\n padding: 11px 0 11px 24px;\n border-top: 1px solid rgba(18, 37, 29, .15);\n}\n\n.rich-text li::before {\n content: \"\";\n position: absolute;\n top: 20px;\n left: 2px;\n width: 8px;\n height: 8px;\n border-radius: 2px;\n background: var(--green);\n rotate: 45deg;\n}\n\n.docs-list {\n display: grid;\n margin: 0;\n padding: 0;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n list-style: none;\n}\n\n.docs-link {\n display: flex;\n min-height: 128px;\n flex-direction: column;\n justify-content: space-between;\n gap: 24px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: var(--surface);\n color: var(--ink-950);\n padding: 22px;\n text-decoration: none;\n transition: border-color 160ms var(--ease), translate 160ms var(--ease);\n}\n\n.docs-link:hover {\n border-color: var(--green);\n color: var(--green-dark);\n translate: 0 -3px;\n}\n\n.docs-link strong {\n font-size: 16px;\n}\n\n.docs-link span {\n color: var(--ink-600);\n font-size: 13px;\n}\n\n.text-link {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n font-weight: 750;\n}\n\n.final-call {\n position: relative;\n overflow: hidden;\n contain: paint;\n border: 1px solid var(--ink-950);\n border-radius: 28px;\n background: var(--green);\n color: #fff;\n padding: clamp(36px, 7vw, 76px);\n}\n\n.final-call::after {\n content: \"\";\n position: absolute;\n right: -80px;\n bottom: -120px;\n width: 320px;\n height: 320px;\n border: 58px solid var(--sun);\n border-radius: 50%;\n opacity: .98;\n}\n\n.final-call h2 {\n position: relative;\n z-index: 1;\n max-width: 700px;\n font-size: clamp(36px, 6vw, 66px);\n}\n\n.final-call p {\n position: relative;\n z-index: 1;\n max-width: 55ch;\n margin: 22px 0 0;\n color: #dff8e9;\n font-size: 17px;\n}\n\n.final-call .hero-actions {\n position: relative;\n z-index: 1;\n}\n\n.final-call .button-primary {\n border-color: var(--sun);\n background: var(--sun);\n color: var(--ink-950);\n}\n\n.final-call .button-primary:hover {\n border-color: #ffe277;\n background: #ffe277;\n color: var(--ink-950);\n}\n\n.final-call .button-secondary {\n border-color: rgba(255, 255, 255, .5);\n background: transparent;\n color: #fff;\n}\n\n.final-call .button-secondary:hover {\n border-color: #fff;\n background: rgba(255, 255, 255, .1);\n color: #fff;\n}\n\n.site-footer {\n padding-block: 34px 44px;\n}\n\n.footer-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 30px;\n}\n\n.footer-note {\n max-width: 680px;\n margin: 13px 0 0;\n color: var(--ink-600);\n font-size: 13px;\n}\n\n.footer-links {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 6px 18px;\n font-size: 13px;\n font-weight: 650;\n}\n\n.footer-links a {\n min-height: 44px;\n display: inline-flex;\n align-items: center;\n}\n\n.not-found {\n display: grid;\n min-height: 100svh;\n place-items: center;\n padding: 40px 24px;\n}\n\n.not-found-card {\n position: relative;\n width: min(720px, 100%);\n overflow: hidden;\n border: 1px solid var(--ink-950);\n border-radius: 28px;\n background: var(--surface);\n padding: clamp(32px, 8vw, 72px);\n box-shadow: 18px 18px 0 var(--sun);\n}\n\n.not-found-code {\n margin: 0 0 28px;\n color: var(--coral);\n font: 800 clamp(60px, 15vw, 132px)/.8 var(--display);\n letter-spacing: -.07em;\n}\n\n.not-found h1 {\n max-width: 620px;\n font-size: clamp(34px, 7vw, 58px);\n line-height: 1.06;\n}\n\n.not-found p {\n max-width: 50ch;\n margin: 22px 0 0;\n color: var(--ink-600);\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .hero-copy,\n .hero-actions,\n .hero-proof {\n animation: rise-in 620ms var(--ease) both;\n }\n\n .hero-actions {\n animation-delay: 100ms;\n }\n\n .hero-proof {\n animation-delay: 180ms;\n }\n\n .terminal {\n animation: terminal-in 720ms 80ms var(--ease) both;\n }\n\n .orbit-dot {\n animation: pulse 2.8s var(--ease) infinite;\n }\n\n @keyframes rise-in {\n from { opacity: 0; translate: 0 16px; }\n to { opacity: 1; translate: 0; }\n }\n\n @keyframes terminal-in {\n from { opacity: 0; translate: -46% -46%; }\n to { opacity: 1; translate: -50% -50%; }\n }\n\n @keyframes pulse {\n 50% { scale: 1.14; }\n }\n}\n\n@media (max-width: 900px) {\n .hero {\n min-height: auto;\n grid-template-columns: 1fr;\n padding-block: 54px 90px;\n }\n\n .launch-visual {\n min-height: 410px;\n }\n\n .section-heading,\n .install-layout,\n .feature-layout {\n grid-template-columns: 1fr;\n }\n\n .install-intro,\n .feature-spotlight {\n position: static;\n }\n\n .install-intro {\n display: grid;\n align-items: end;\n grid-template-columns: 1fr 1fr;\n gap: 30px;\n }\n\n .security-note {\n margin: 0;\n }\n\n .feature-spotlight {\n min-height: 310px;\n }\n}\n\n@media (max-width: 720px) {\n .shell {\n width: min(100% - 32px, 1180px);\n }\n\n .site-nav {\n min-height: 68px;\n }\n\n .nav-link {\n display: none;\n }\n\n .hero {\n gap: 42px;\n padding-top: 46px;\n }\n\n h1 {\n font-size: clamp(44px, 14vw, 68px);\n }\n\n .hero-copy {\n font-size: 17px;\n }\n\n .launch-visual {\n min-height: 350px;\n }\n\n .orbit-one {\n width: min(94%, 350px);\n }\n\n .orbit-two {\n width: min(72%, 270px);\n }\n\n .terminal {\n box-shadow: 11px 11px 0 var(--sun);\n }\n\n .terminal-body {\n min-height: 210px;\n padding: 22px 18px;\n }\n\n .section-heading {\n align-items: start;\n gap: 18px;\n margin-bottom: 38px;\n }\n\n .install-intro {\n display: block;\n }\n\n .security-note {\n margin-top: 24px;\n }\n\n .steps,\n .detail-grid,\n .docs-list {\n grid-template-columns: 1fr;\n }\n\n .step {\n min-height: auto;\n border-bottom: 0;\n padding: 24px 4px;\n }\n\n .step + .step {\n border-left: 0;\n }\n\n .step:last-child {\n border-bottom: 1px solid var(--line);\n }\n\n .step::before {\n margin-bottom: 16px;\n }\n\n .agent-panel-head,\n .footer-row {\n align-items: flex-start;\n flex-direction: column;\n }\n\n .agent-table th,\n .agent-table td {\n padding-inline: 14px;\n }\n\n .agent-table th:nth-child(4),\n .agent-table td:nth-child(4) {\n display: none;\n }\n\n .footer-links {\n justify-content: flex-start;\n }\n\n .final-call::after {\n right: -140px;\n bottom: -180px;\n }\n}\n\n@media (max-width: 480px) {\n .brand {\n font-size: 16px;\n }\n\n .brand-mark {\n width: 32px;\n height: 32px;\n }\n\n .language-link {\n padding-inline: 10px;\n }\n\n .hero-actions,\n .hero-actions .button {\n width: 100%;\n }\n\n .hero-proof {\n display: grid;\n grid-template-columns: 1fr 1fr;\n }\n\n .launch-visual {\n min-height: 320px;\n }\n\n .terminal {\n border-radius: 17px;\n }\n\n .terminal-bar {\n min-height: 42px;\n }\n\n .terminal-body {\n min-height: 190px;\n font-size: 11px;\n }\n\n .command-head {\n padding-left: 14px;\n }\n\n .copy-button {\n min-width: 82px;\n }\n\n .command-code {\n padding: 19px 14px;\n font-size: 11px;\n }\n\n .feature-spotlight {\n min-height: 330px;\n }\n\n .feature-row {\n grid-template-columns: 34px 1fr;\n gap: 10px;\n }\n\n .agent-table th:first-child,\n .agent-table td:first-child,\n .agent-table th:nth-child(4),\n .agent-table td:nth-child(4) {\n display: none;\n }\n\n .agent-table {\n font-size: 12px;\n }\n\n .agent-table th,\n .agent-table td {\n padding-inline: 10px;\n }\n\n .agent-legend {\n display: none;\n }\n\n .not-found-card {\n box-shadow: 10px 10px 0 var(--sun);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n html {\n scroll-behavior: auto;\n }\n\n *,\n *::before,\n *::after {\n animation-duration: .01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: .01ms !important;\n }\n}\n\n@media print {\n .site-nav,\n .hero-actions,\n .copy-button,\n .final-call,\n .site-footer {\n display: none;\n }\n\n body {\n background: #fff;\n }\n\n .section,\n .hero {\n min-height: auto;\n padding-block: 28px;\n }\n}\n","type":"text/css; charset=utf-8","etag":"\"93e52ab55ce71853\""},"/assets/site.js":{"body":"(function () {\n \"use strict\";\n\n var copyButtons = document.querySelectorAll(\"[data-copy]\");\n\n function legacyCopy(text) {\n var input = document.createElement(\"textarea\");\n input.value = text;\n input.setAttribute(\"readonly\", \"\");\n input.style.position = \"fixed\";\n input.style.opacity = \"0\";\n document.body.appendChild(input);\n input.select();\n var copied = document.execCommand(\"copy\");\n input.remove();\n return copied ? Promise.resolve() : Promise.reject(new Error(\"copy failed\"));\n }\n\n function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) {\n return navigator.clipboard.writeText(text);\n }\n return legacyCopy(text);\n }\n\n copyButtons.forEach(function (button) {\n var idleLabel = button.getAttribute(\"data-label-idle\") || \"Copy\";\n var successLabel = button.getAttribute(\"data-label-success\") || \"Copied\";\n var errorLabel = button.getAttribute(\"data-label-error\") || \"Select command\";\n var label = button.querySelector(\"[data-copy-label]\");\n var timer;\n\n button.addEventListener(\"click\", function () {\n window.clearTimeout(timer);\n copyText(button.getAttribute(\"data-copy\") || \"\").then(function () {\n button.setAttribute(\"data-state\", \"success\");\n label.textContent = successLabel;\n }).catch(function () {\n button.setAttribute(\"data-state\", \"error\");\n label.textContent = errorLabel;\n }).finally(function () {\n timer = window.setTimeout(function () {\n button.removeAttribute(\"data-state\");\n label.textContent = idleLabel;\n }, 1800);\n });\n });\n });\n}());\n","type":"text/javascript; charset=utf-8","etag":"\"e32ad7eff5bfef9a\""},"/assets/favicon.svg":{"body":"\n \n \n\n","type":"image/svg+xml; charset=utf-8","etag":"\"97f7d8aaef171778\""}}); diff --git a/cloudflare/worker.js b/cloudflare/worker.js index e514c85..0187f29 100644 --- a/cloudflare/worker.js +++ b/cloudflare/worker.js @@ -6,7 +6,7 @@ import { WEB_ASSETS } from "./web-assets.js"; const REPO = "bit-cook/AgentBoot"; -const TAG = "v1.2.0"; +const TAG = "v1.3.0"; const GH_REL = `https://github.com/${REPO}/releases/download/${TAG}`; const ASSET_CACHE = "public, max-age=31536000, immutable"; const PAGE_CACHE = "public, max-age=300, stale-while-revalidate=3600"; diff --git a/core/agent.py b/core/agent.py index 9b88a7e..7d51cf9 100644 --- a/core/agent.py +++ b/core/agent.py @@ -27,7 +27,7 @@ try: VERSION = open(os.path.join(APP_DIR, "VERSION"), "r", encoding="ascii").read().strip() except OSError: - VERSION = "1.2.0" + VERSION = "1.3.0" AB_HOME = os.environ.get("AGENTBOOT_HOME") or os.path.join(os.path.expanduser("~"), ".agentboot") CONFIG_PATH = os.path.join(AB_HOME, "config.json") KB_DIR = os.path.join(APP_DIR, "tools", "linux-kb") diff --git a/core/menu.py b/core/menu.py index 026a0b0..9c7a3da 100644 --- a/core/menu.py +++ b/core/menu.py @@ -41,6 +41,7 @@ AGENTS_DIR = os.path.join(AB_HOME, "agents") NPM_PREFIX = os.path.join(AB_HOME, "npm-prefix") ENV_JSON = os.path.join(AB_HOME, "env.json") +APPS_DIR = os.path.join(AB_HOME, "apps") NODE_VERSION = "v22.23.2" NPM_MIRROR = "https://registry.npmmirror.com" @@ -54,6 +55,21 @@ POSIX = os.name != "nt" +CURSOR_VERSION = "3.18" +CURSOR_DOWNLOADS = { + "linux-x64": "https://api2.cursor.sh/updates/download/golden/linux-x64/cursor/3.18", + "linux-arm64": "https://api2.cursor.sh/updates/download/golden/linux-arm64/cursor/3.18", + "darwin-x64": "https://api2.cursor.sh/updates/download/golden/darwin-x64/cursor/3.18", + "darwin-arm64": "https://api2.cursor.sh/updates/download/golden/darwin-arm64/cursor/3.18", + "win-x64": "https://api2.cursor.sh/updates/download/golden/win32-x64-user/cursor/3.18", + "win-arm64": "https://api2.cursor.sh/updates/download/golden/win32-arm64-user/cursor/3.18", +} +CURSOR_MAX_BYTES = 600 * 1024 * 1024 +CURSOR_SHA256 = { + "linux-x64": "44f3760fbb63d082bafa6562001ca66e9be803274c4c7642215e20de92490664", + "linux-arm64": "ec03879fca957065623d2274b39cd814d3209692cb4864f25859284cfb1302ec", +} + # ---------------------------------------------------------------- 基础工具 @@ -504,7 +520,7 @@ def prefix_bin_dirs(): def find_bin(name): cands = [name + (".cmd" if not POSIX else "")] if not POSIX else [name] - for d in prefix_bin_dirs(): + for d in [os.path.join(AB_HOME, "bin")] + prefix_bin_dirs(): for c in cands: p = os.path.join(d, c) if os.path.exists(p): @@ -554,6 +570,16 @@ def detect_install(a): if os.path.exists(coco_shim): return {"status": "legacy", "managed": True, "source": "online", "entry": {"executable": coco_shim}} + if a["id"] == "cursor" and os.path.isdir(_cursor_root()): + try: + with open(_cursor_marker(), "r", encoding="utf-8") as source: + marker = json.load(source) + if marker.get("managed_by") == "AgentBoot": + return {"status": "legacy", "managed": True, "source": "online", + "entry": {"method": "cursor", "executable": os.path.join( + AB_HOME, "bin", "cursor" + ("" if POSIX else ".cmd"))}} + except (OSError, ValueError, TypeError): + pass if found: return {"status": "external", "managed": False, "source": None, "entry": {}, "executable": found} @@ -678,6 +704,11 @@ def uninstall_one(a, purge=False): _remove_coco_external_launchers(entry) except OSError as e: return False, str(e) + elif method == "cursor": + try: + _remove_cursor_install() + except OSError as e: + return False, str(e) elif source == "offline": try: shutil.rmtree(os.path.join(AGENTS_DIR, a["id"]), ignore_errors=False) @@ -842,6 +873,8 @@ def install_online(ids): ok = install_hermes_special(a) if not ok: log_err("专用流程失败:可检查 Git 是否安装、或配置代理后重试") + elif a.get("special_install") == "cursor": + ok = install_cursor(a) elif method == "npm": ok = aid in batched or npm_install(a["npm"], a.get("node"), npm_context) elif method == "script": @@ -948,6 +981,254 @@ def _download_script(url, suffix): raise +def _download_cursor_asset(url, suffix): + """Download one pinned official Cursor asset and return path/final-url/SHA-256.""" + import hashlib + import tempfile + from urllib import request as urllib_request + request = urllib_request.Request(url, headers={"User-Agent": "AgentBoot/%s" % VERSION}) + fd, path = tempfile.mkstemp(prefix="agentboot-cursor-", suffix=suffix) + try: + total = 0 + digest = hashlib.sha256() + with os.fdopen(fd, "wb") as output, urllib_request.urlopen(request, timeout=180) as response: + final = urllib.parse.urlsplit(response.geturl()) + if final.scheme != "https" or final.hostname not in ("api2.cursor.sh", "downloads.cursor.com"): + raise ValueError("Cursor 下载重定向到了非官方地址") + length = int(response.headers.get("Content-Length") or 0) + if length and length > CURSOR_MAX_BYTES: + raise ValueError("Cursor 安装包超过大小限制") + for chunk in iter(lambda: response.read(1 << 20), b""): + total += len(chunk) + if total > CURSOR_MAX_BYTES: + raise ValueError("Cursor 安装包超过大小限制") + digest.update(chunk) + output.write(chunk) + if total < 1024 * 1024: + raise ValueError("Cursor 安装包异常过小") + return path, response.geturl(), digest.hexdigest() + except Exception: + try: + os.close(fd) + except OSError: + pass + try: + os.remove(path) + except OSError: + pass + raise + + +def _cursor_root(): + return os.path.join(APPS_DIR, "cursor") + + +def _cursor_marker(root=None): + return os.path.join(root or _cursor_root(), "agentboot-managed.json") + + +def _write_cursor_marker(root, final_url, digest): + marker = {"managed_by": "AgentBoot", "version": CURSOR_VERSION, + "source": final_url, "sha256": digest} + with open(_cursor_marker(root), "w", encoding="utf-8", newline="\n") as output: + json.dump(marker, output, ensure_ascii=False, indent=2) + output.write("\n") + + +def _write_cursor_shim(executable, app_bundle=None): + os.makedirs(os.path.join(AB_HOME, "bin"), exist_ok=True) + if POSIX: + path = os.path.join(AB_HOME, "bin", "cursor") + if app_bundle: + cli = os.path.join(app_bundle, "Contents", "Resources", "app", "bin", "cursor") + body = ('if [ -x "%s" ]; then exec "%s" "$@"; fi\n' + 'exec open "%s" --args "$@"\n') % (cli, cli, app_bundle) + else: + body = 'exec "%s" "$@"\n' % executable + with open(path, "w", encoding="utf-8", newline="\n") as output: + output.write("#!/bin/sh\n# AgentBoot Cursor launcher\n" + body) + os.chmod(path, 0o755) + else: + path = os.path.join(AB_HOME, "bin", "cursor.cmd") + with open(path, "w", encoding="utf-8-sig", newline="") as output: + output.write('@echo off\r\nrem AgentBoot Cursor launcher\r\nstart "" "%s" %%*\r\n' % executable) + return path + + +def _cursor_candidate(): + root = _cursor_root() + return root + ".new.%s" % os.getpid() + + +def _commit_cursor_candidate(candidate): + root = _cursor_root() + backup = root + ".old.%s" % os.getpid() + shutil.rmtree(backup, ignore_errors=True) + if os.path.isdir(root): + os.replace(root, backup) + try: + os.replace(candidate, root) + shutil.rmtree(backup, ignore_errors=True) + except Exception: + if os.path.isdir(backup): + os.replace(backup, root) + raise + return root + + +def _powershell_executable(): + return shutil.which("powershell.exe") or shutil.which("pwsh.exe") or \ + shutil.which("pwsh") or shutil.which("powershell") + + +def _verify_cursor_windows_signature(path, shell=None): + shell = shell or _powershell_executable() + if not shell: + return False + result = subprocess.run( + [shell, "-NoProfile", "-NonInteractive", "-Command", + "$s=Get-AuthenticodeSignature -LiteralPath $args[0]; " + "if($s.Status -ne 'Valid' -or $s.SignerCertificate.Subject -notmatch 'Anysphere'){exit 1}", path], + timeout=60) + return result.returncode == 0 + + +def install_cursor(a): + """Install pinned official Cursor into an AgentBoot-owned per-user directory.""" + pid = plat_id() + url = CURSOR_DOWNLOADS.get(pid) + if not url: + log_err("Cursor 暂不支持当前平台:%s" % pid) + return False + suffix = ".AppImage" if pid.startswith("linux") else (".dmg" if pid.startswith("darwin") else ".exe") + path = None + candidate = _cursor_candidate() + root = _cursor_root() + if os.path.islink(APPS_DIR): + log_err("Cursor 应用根目录是符号链接,拒绝安装") + return False + if os.path.exists(root): + if os.path.islink(root) or not _inside(os.path.realpath(root), os.path.realpath(APPS_DIR)): + log_err("Cursor 私有目录位于 AgentBoot 应用根目录之外,拒绝覆盖") + return False + try: + with open(_cursor_marker(root), "r", encoding="utf-8") as source: + marker = json.load(source) + except (OSError, ValueError, TypeError): + log_err("Cursor 私有目录缺少 AgentBoot 归属标记,拒绝覆盖") + return False + if marker.get("managed_by") != "AgentBoot": + log_err("Cursor 私有目录归属标记无效,拒绝覆盖") + return False + shutil.rmtree(candidate, ignore_errors=True) + try: + log_info("下载 Cursor 官方版本 %s:%s" % (CURSOR_VERSION, url)) + path, final_url, digest = _download_cursor_asset(url, suffix) + os.makedirs(candidate, mode=0o700, exist_ok=True) + if pid.startswith("linux"): + if digest != CURSOR_SHA256.get(pid): + raise ValueError("Cursor AppImage SHA-256 校验失败") + executable = os.path.join(candidate, "Cursor.AppImage") + shutil.move(path, executable) + path = None + os.chmod(executable, 0o755) + with open(executable, "rb") as source: + if source.read(4) != b"\x7fELF": + raise ValueError("Cursor AppImage 不是有效 ELF 文件") + check = subprocess.run([executable, "--appimage-version"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=20) + if check.returncode != 0: + raise ValueError("Cursor AppImage 自检失败") + _write_cursor_marker(candidate, final_url, digest) + _write_cursor_shim(os.path.join(root, "Cursor.AppImage")) + _commit_cursor_candidate(candidate) + elif pid.startswith("darwin"): + mount = candidate + ".mount" + os.makedirs(mount, exist_ok=True) + try: + subprocess.run(["hdiutil", "attach", "-readonly", "-nobrowse", "-mountpoint", mount, path], + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=60) + app = os.path.join(mount, "Cursor.app") + if not os.path.isdir(app): + raise ValueError("Cursor DMG 中缺少 Cursor.app") + subprocess.run(["codesign", "--verify", "--deep", "--strict", app], check=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=60) + subprocess.run(["spctl", "--assess", "--type", "execute", app], check=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=60) + installed = os.path.join(candidate, "Cursor.app") + subprocess.run(["ditto", app, installed], check=True, timeout=180) + subprocess.run(["codesign", "--verify", "--deep", "--strict", installed], check=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=60) + subprocess.run(["spctl", "--assess", "--type", "execute", installed], check=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=60) + finally: + subprocess.run(["hdiutil", "detach", mount], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, timeout=30) + shutil.rmtree(mount, ignore_errors=True) + _write_cursor_marker(candidate, final_url, digest) + _write_cursor_shim(None, os.path.join(root, "Cursor.app")) + _commit_cursor_candidate(candidate) + else: + shell = _powershell_executable() + if not shell: + raise ValueError("缺少 PowerShell,无法验证 Cursor Authenticode 签名") + if not _verify_cursor_windows_signature(path, shell): + raise ValueError("Cursor Windows 安装器签名验证失败") + shutil.rmtree(candidate, ignore_errors=True) + command = [path, "/VERYSILENT", "/NORESTART", "/MERGETASKS=!runcode", "/DIR=%s" % candidate] + if subprocess.run(command, timeout=300).returncode != 0: + raise ValueError("Cursor Windows 安装器执行失败") + executable = os.path.join(candidate, "Cursor.exe") + if not os.path.isfile(executable): + raise ValueError("Cursor Windows 安装目录缺少 Cursor.exe") + if not _verify_cursor_windows_signature(executable, shell): + raise ValueError("Cursor.exe 签名验证失败") + _write_cursor_marker(candidate, final_url, digest) + _write_cursor_shim(os.path.join(root, "Cursor.exe")) + _commit_cursor_candidate(candidate) + return True + except Exception as error: + log_err("Cursor 安装失败:%s" % error) + shutil.rmtree(candidate, ignore_errors=True) + if not os.path.exists(root): + shim = os.path.join(AB_HOME, "bin", "cursor" + ("" if POSIX else ".cmd")) + try: + with open(shim, "r", encoding="utf-8", errors="ignore") as source: + owned = "AgentBoot Cursor launcher" in source.read(256) + if owned: + os.remove(shim) + except OSError: + pass + return False + finally: + if path: + try: + os.remove(path) + except OSError: + pass + + +def _remove_cursor_install(): + root = _cursor_root() + if not os.path.exists(root): + return + if not _inside(root, APPS_DIR) or os.path.islink(root): + raise OSError("拒绝删除 AgentBoot 应用目录之外的 Cursor") + try: + with open(_cursor_marker(root), "r", encoding="utf-8") as source: + marker = json.load(source) + except (OSError, ValueError, TypeError): + raise OSError("Cursor 缺少 AgentBoot 归属标记,拒绝自动删除") + if marker.get("managed_by") != "AgentBoot": + raise OSError("Cursor 归属标记无效,拒绝自动删除") + if os.name == "nt": + uninstallers = [name for name in os.listdir(root) if re.match(r"unins\d*\.exe$", name, re.I)] + if uninstallers: + subprocess.run([os.path.join(root, uninstallers[0]), "/VERYSILENT", "/NORESTART"], timeout=180) + if os.path.exists(root): + shutil.rmtree(root) + + def install_via_pip(a): py = shutil.which("python3") or shutil.which("python") if not py: @@ -1377,7 +1658,7 @@ def offline_install(ids, payload_dir=None): agents = {a["id"]: a for a in load_registry()} selected_npm = [agents[aid] for aid in ids - if aid in agents and agents[aid].get("method") == "npm"] + if aid in agents and agents[aid].get("method") == "npm" and aid != "opencode"] # Bundled Node is the reproducible default and every selected Agent is checked individually below. strictest_node = ">=18" @@ -1445,7 +1726,7 @@ def offline_install(ids, payload_dir=None): log_err(t("menu.offline_no_payload") % (a["name"], pid)) fail_list.append(aid) continue - if not offline_node or not node_ok(offline_node, a.get("node")): + if aid != "opencode" and (not offline_node or not node_ok(offline_node, a.get("node"))): log_err(t("menu.offline_no_node") % a["name"]) fail_list.append(aid) continue @@ -1480,8 +1761,18 @@ def offline_install(ids, payload_dir=None): if os.path.isdir(backup): os.replace(backup, dst) fail_list.append(aid) continue + entry_override = None + if aid == "opencode": + entry_override = select_opencode_binary(a, dst, pid) + if not entry_override: + log_err("OpenCode 离线载荷没有可运行的官方平台 binary") + shutil.rmtree(dst, ignore_errors=True) + if os.path.isdir(backup): os.replace(backup, dst) + fail_list.append(aid) + continue env_extra, args_prefix = wire_agnes(a) - shim = write_shim(a, env_extra, args_prefix, node_path=offline_node) + shim = write_shim(a, env_extra, args_prefix, node_path=offline_node, + entry_override=entry_override) if shim: shutil.rmtree(backup, ignore_errors=True) record_install(a, "offline", os.path.join( @@ -1547,6 +1838,33 @@ def offline_npm_entry(a): return None +def opencode_binary_candidates(a, root, platform_id): + """Return pinned official OpenCode native binaries bundled for one target.""" + result = [] + binary = "opencode.exe" if platform_id.startswith("win-") else "opencode" + for package in (a.get("offline_binary_packages") or {}).get(platform_id, []): + for base in (os.path.join(root, "node_modules", "opencode-ai", "node_modules"), + os.path.join(root, "node_modules")): + path = os.path.join(base, package, "bin", binary) + if os.path.isfile(path) and os.path.getsize(path) > 10 * 1024 * 1024: + result.append(path) + break + return result + + +def select_opencode_binary(a, root, platform_id): + """Select regular/AVX2 or baseline binary by executing the official version probe.""" + for candidate in opencode_binary_candidates(a, root, platform_id): + try: + result = subprocess.run([candidate, "--version"], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, timeout=20) + if result.returncode == 0: + return candidate + except (OSError, subprocess.TimeoutExpired): + continue + return None + + def _npm_entry_kind(entry): extension = os.path.splitext(entry)[1].lower() if extension in (".js", ".mjs", ".cjs"): @@ -1563,7 +1881,7 @@ def _npm_entry_kind(entry): return "direct" -def write_shim(a, env_extra=None, args_prefix=None, node_path=None): +def write_shim(a, env_extra=None, args_prefix=None, node_path=None, entry_override=None): """为离线安装的 Agent 生成启动 shim。""" aid, bin_ = a["id"], a["bin"] env_extra = env_extra or {} @@ -1584,7 +1902,7 @@ def write_shim(a, env_extra=None, args_prefix=None, node_path=None): nd = runtime_node_dir() if os.path.isdir(nd): lines.append('[ -x "%s/bin/node" ] && export PATH="%s/bin:$PATH"' % (nd, nd)) - entry = offline_npm_entry(a) + entry = entry_override or offline_npm_entry(a) if not entry: raise ValueError("未找到 %s 的真实 npm bin 入口" % aid) pre = " ".join('"%s"' % x for x in args_prefix) @@ -1609,7 +1927,7 @@ def write_shim(a, env_extra=None, args_prefix=None, node_path=None): else: lines = ["rem AgentBoot shim (Agnes preset)"] lines += _agnes_env_cmd_lines(env_extra) - entry = offline_npm_entry(a) + entry = entry_override or offline_npm_entry(a) if not entry: raise ValueError("未找到 %s 的真实 npm bin 入口" % aid) pre = " ".join(args_prefix) diff --git a/docs/en/install-guide.md b/docs/en/install-guide.md index 1ebb748..16eefe2 100644 --- a/docs/en/install-guide.md +++ b/docs/en/install-guide.md @@ -1,7 +1,7 @@ # AgentBoot Installation Guide -> **v1.2.0** · Linux / macOS / Windows · CLI UI in Chinese by default (switchable to English: `agentboot lang en`) -> AgentBoot is a minimal, fast, ready-to-run AI Agent launcher: it ships a built-in fallback agent (free **Agnes** model by default), while the other agents (Claude Code, Codex, Qwen Code, OpenCode, CodeBuddy, MiMo Code, Cline, Pi, CoCo, …) are installed **from a menu of your choice**. +> **v1.3.0** · Linux / macOS / Windows · CLI UI in Chinese by default (switchable to English: `agentboot lang en`) +> AgentBoot is a minimal, fast, ready-to-run AI Agent launcher: it ships a built-in fallback agent while Cursor, OpenCode, Claude Code, Codex and the other agents are installed from a menu of your choice. Cursor and Aider are online-only; OpenCode uses pinned official native binaries in verified offline packs. **目录 / Table of contents**: [One-command online install](#one-command-online-install) · [One-command offline install](#one-command-offline-install) · [Custom agents](#custom-agents-beyond-the-registry) · [Custom offline packages](#custom-offline-packages-slim) · [China network](#china-network-adaptive) · [Model providers](#model-providers) · [Built-in agent](#built-in-agent-ab) · [Upgrade & uninstall](#upgrade--uninstall) · [Troubleshooting](#online-install-troubleshooting) · [FAQ](#faq) @@ -43,9 +43,9 @@ Three steps: **① get an offline package → ② copy & extract → ③ run the ### Step 1 — Get an offline package - **Option A**: download from [Releases](https://github.com/bit-cook/AgentBoot/releases) - - `AgentBoot-offline-v1.2.0-win-x64-codex.zip` (Windows x64) - - `AgentBoot-offline-v1.2.0-linux-x64-codex.tar.gz` (Linux x64) - - `AgentBoot-offline-v1.2.0-linux-x64-codex-sfx.sh` (Linux x64 self-extracting file) + - `AgentBoot-offline-v1.3.0-win-x64-codex.zip` (Windows x64) + - `AgentBoot-offline-v1.3.0-linux-x64-codex.tar.gz` (Linux x64) + - `AgentBoot-offline-v1.3.0-linux-x64-codex-sfx.sh` (Linux x64 self-extracting file) - **Option B — build your own slim package** (recommended; see [Custom offline packages](#custom-offline-packages-slim)): pick platforms and agents, output goes to `dist/`. @@ -54,9 +54,9 @@ Three steps: **① get an offline package → ② copy & extract → ③ run the | Target machine | How | |---|---| | Windows (any) | Right-click the ZIP → Extract All (built into Explorer) | -| Windows 10/11 (CLI) | `tar -xf AgentBoot-offline-v1.2.0-win-x64-codex.zip` | -| Linux x64 | `tar -xzf AgentBoot-offline-v1.2.0-linux-x64-codex.tar.gz -C ~` | -| Minimal Linux x64 | `sh AgentBoot-offline-v1.2.0-linux-x64-codex-sfx.sh` | +| Windows 10/11 (CLI) | `tar -xf AgentBoot-offline-v1.3.0-win-x64-codex.zip` | +| Linux x64 | `tar -xzf AgentBoot-offline-v1.3.0-linux-x64-codex.tar.gz -C ~` | +| Minimal Linux x64 | `sh AgentBoot-offline-v1.3.0-linux-x64-codex-sfx.sh` | ### Step 3 — Run the offline installer @@ -135,7 +135,7 @@ Stored in `~/.agentboot/custom-agents.json` — survives upgrades. Same install ## 📦 Custom offline packages (slim) -The v1.1 Release provides verified Codex slim packs. Build a target-platform pack with only what you need: +The v1.3 Release provides verified Codex and OpenCode slim packs. Build a target-platform pack with only what you need: **Menu**: `agentboot` → `[7] Build custom offline package` → pick platforms → pick agents → build. diff --git "a/docs/zh/\345\256\211\350\243\205\346\214\207\345\215\227.md" "b/docs/zh/\345\256\211\350\243\205\346\214\207\345\215\227.md" index 9f9537f..539b1c7 100644 --- "a/docs/zh/\345\256\211\350\243\205\346\214\207\345\215\227.md" +++ "b/docs/zh/\345\256\211\350\243\205\346\214\207\345\215\227.md" @@ -1,8 +1,8 @@ # AgentBoot 安装指南 -> **版本 v1.2.0** · 支持 Linux / macOS / Windows · 界面默认中文(切换:`agentboot lang en`) +> **版本 v1.3.0** · 支持 Linux / macOS / Windows · 界面默认中文(切换:`agentboot lang en`) > 语言 / Language: 中文(本页) | [English](../en/install-guide.md) | [根目录中文指南](../../安装指南.md) -> AgentBoot 是一个极简、极速、开箱即用的 AI Agent 启动器:内置保底 Agent,其余 Agent 通过菜单自选安装。离线能力以注册表和目标平台构建验证为准;Aider 与 OpenCode 当前仅在线安装,Hermes 必须在目标平台原生构建。 +> AgentBoot 是一个极简、极速、开箱即用的 AI Agent 启动器:内置保底 Agent,其余 Agent(含 Cursor)通过菜单自选安装。离线能力以注册表和目标平台构建验证为准;Cursor 与 Aider 当前仅在线安装,OpenCode 使用固定官方 native binary 离线包,Hermes 必须在目标平台原生构建。 --- @@ -61,9 +61,9 @@ curl -fsSL https://bit-cook.github.io/AgentBoot/install.sh | sh - **方式 A(推荐)**:到 GitHub Releases 下载现成的离线包 `https://github.com/bit-cook/AgentBoot/releases` → 选择 - - `AgentBoot-offline-v1.2.0-win-x64-codex.zip`(Windows x64) - - `AgentBoot-offline-v1.2.0-linux-x64-codex.tar.gz`(Linux x64) - - `AgentBoot-offline-v1.2.0-linux-x64-codex-sfx.sh`(Linux x64 自解压单文件) + - `AgentBoot-offline-v1.3.0-win-x64-codex.zip`(Windows x64) + - `AgentBoot-offline-v1.3.0-linux-x64-codex.tar.gz`(Linux x64) + - `AgentBoot-offline-v1.3.0-linux-x64-codex-sfx.sh`(Linux x64 自解压单文件) - **方式 B(自建)**:在有网的 Linux/macOS 或 Windows 机器上运行项目自带的构建脚本, 自动从 npmmirror 镜像拉取全部 Agent 离线载荷并打包: @@ -83,9 +83,9 @@ curl -fsSL https://bit-cook.github.io/AgentBoot/install.sh | sh | 目标机器情况 | 解压方法 | |---|---| | **Windows(任意版本)** | 直接**右键 ZIP → 全部解压缩**。Windows 资源管理器自带 ZIP 支持,无需安装任何软件 | -| **Windows 10/11(命令行党)** | 系统自带 `tar`:`tar -xf AgentBoot-offline-v1.2.0-win-x64-codex.zip` | -| **Linux x64** | 系统自带 `tar`:`tar -xzf AgentBoot-offline-v1.2.0-linux-x64-codex.tar.gz -C ~` | -| **Linux x64 极简环境** | 用自解压单文件:`sh AgentBoot-offline-v1.2.0-linux-x64-codex-sfx.sh` | +| **Windows 10/11(命令行党)** | 系统自带 `tar`:`tar -xf AgentBoot-offline-v1.3.0-win-x64-codex.zip` | +| **Linux x64** | 系统自带 `tar`:`tar -xzf AgentBoot-offline-v1.3.0-linux-x64-codex.tar.gz -C ~` | +| **Linux x64 极简环境** | 用自解压单文件:`sh AgentBoot-offline-v1.3.0-linux-x64-codex-sfx.sh` | > 自解压包 `*-sfx.sh` 本身就是一个 shell 脚本 + 内嵌 base64 数据,`sh xxx-sfx.sh` 即自动解压并直接进入安装流程,非常适合只有最小化系统的服务器。 @@ -183,7 +183,7 @@ python core/menu.py add-agent --del myagent ## 📦 自定义离线安装包(按需瘦身) -官方 v1.1 Release 提供经过安装/启动/卸载验证的 Codex 精简包;其他 Agent 请在目标平台自行构建按需包: +官方 v1.3 Release 提供经过安装/启动/卸载验证的 Codex 与 OpenCode 精简包;其他 Agent 请在目标平台自行构建按需包: **方式一:菜单向导(推荐)** diff --git a/install.sh b/install.sh index 1c86fbe..c2abbfb 100755 --- a/install.sh +++ b/install.sh @@ -9,7 +9,7 @@ set -eu REPO="bit-cook/AgentBoot" -TAG="v1.2.0" +TAG="v1.3.0" TARBALL="agentboot-online-${TAG}.tar.gz" BOOT_BASE="https://boot.ide.pub" GH="https://github.com/${REPO}/releases/download/${TAG}" diff --git a/pages/404.html b/pages/404.html index 874843f..e7bac64 100644 --- a/pages/404.html +++ b/pages/404.html @@ -7,7 +7,7 @@ 页面未找到 · AgentBoot - +
diff --git a/pages/agentboot-online-v1.2.0.tar.gz.sha256 b/pages/agentboot-online-v1.2.0.tar.gz.sha256 deleted file mode 100644 index fbb9bed..0000000 --- a/pages/agentboot-online-v1.2.0.tar.gz.sha256 +++ /dev/null @@ -1 +0,0 @@ -5a6ac9b3193dd72859f8b5dab90dccaeb44a03819ee0810ab41ff15867d6f582 agentboot-online-v1.2.0.tar.gz diff --git a/pages/agentboot-online-v1.2.0.zip.sha256 b/pages/agentboot-online-v1.2.0.zip.sha256 deleted file mode 100644 index fb548c1..0000000 --- a/pages/agentboot-online-v1.2.0.zip.sha256 +++ /dev/null @@ -1 +0,0 @@ -bae9d13ec1cab8f3612978e15bc3a315e7873ea53c940e9f1d7da601dcf5031f agentboot-online-v1.2.0.zip diff --git a/pages/agentboot-online-v1.3.0.tar.gz.sha256 b/pages/agentboot-online-v1.3.0.tar.gz.sha256 new file mode 100644 index 0000000..0a9b203 --- /dev/null +++ b/pages/agentboot-online-v1.3.0.tar.gz.sha256 @@ -0,0 +1 @@ +dbf81b1dbad4980135c8f39ab086216dbc1b6528d155b2ec5da2349859c20b67 agentboot-online-v1.3.0.tar.gz diff --git a/pages/agentboot-online-v1.3.0.zip.sha256 b/pages/agentboot-online-v1.3.0.zip.sha256 new file mode 100644 index 0000000..531f96f --- /dev/null +++ b/pages/agentboot-online-v1.3.0.zip.sha256 @@ -0,0 +1 @@ +60afab4bc90117aa2cf123837b2c9695578878a332b70c57881ad8f83c08bd4d agentboot-online-v1.3.0.zip diff --git a/pages/en/index.html b/pages/en/index.html index e906cee..0158e37 100644 --- a/pages/en/index.html +++ b/pages/en/index.html @@ -6,13 +6,13 @@ AgentBoot · Launch your AI agents with one command - + - - + + @@ -33,7 +33,7 @@
-

v1.2.0 · Free and open source · Three platforms

+

v1.3.0 · Free and open source · Three platforms

One command.
Agents ready.

Bring installation, models, mirrors, offline packs, and safe uninstall into one clear menu. You choose the AI agents you need; AgentBoot installs them reliably.

@@ -47,7 +47,7 @@

One command.
Agents ready.

    -
  • Choose from 14 agents
  • +
  • Choose from 15 agents
  • Linux · macOS · Windows
  • Offline installation
  • MIT License
  • @@ -137,7 +137,7 @@

    Even if other agents won't install, ab works right away

      -
    1. 01

      Install on demand, not as a bundle

      Choose from 14 mainstream agents, including Claude Code, Codex, Qwen Code, OpenCode, MiMo, Cline, Pi, and CoCo.

    2. +
    3. 01

      Install on demand, not as a bundle

      Choose from 15 mainstream agents, including Cursor, Claude Code, Codex, Qwen Code, OpenCode, MiMo, Cline, Pi, and CoCo.

    4. 02

      Manage model providers in one place

      Agnes works with zero configuration. You can also name and manage any OpenAI-compatible endpoint, Ollama, or LM Studio, and set a failover order.

    5. 03

      Automatically adapt to networks in China

      AgentBoot selects npm, Node, and PyPI mirrors automatically, fails over across Worker, Pages, and Release sources, and configures a proxy in one step.

    6. 04

      Offline packs are tested end to end

      Release packs pass install, launch, and uninstall smoke tests; menu [7] can also build a custom pack on demand on the target platform.

    7. @@ -150,13 +150,13 @@

      Even if other agents won't install, ab works right away

      -

      Your toolbox

      14 agents. One front door.

      +

      Your toolbox

      15 agents. One front door.

      Choose agents as needed for online installation. Agents marked as offline-ready can be packaged with their dependencies and runtimes.

      -
      14built-in choices
      +
      15built-in choices
      A green dot indicates support for offline installation
      @@ -164,7 +164,7 @@

      Even if other agents won't install, ab works right away

      #AgentCommandVendorOffline 01CoCo AgentcocoBitCookLinux/macOS - 02OpenCodeopencodeopencode.aiOnline only + 02OpenCodeopencodeopencode.aiReady 03Hermes AgenthermesHermesRequires Git 04Cline CLIclineClineSupported 05CodeBuddy CLIcodebuddyTencentSupported @@ -177,6 +177,7 @@

      Even if other agents won't install, ab works right away

      12Gemini CLIgeminiGoogleSupported 13iFlow CLIiflowiFlowSupported 14AideraiderAider AIOnline only + 15CursorcursorAnysphereOnline only
      @@ -190,7 +191,7 @@

      Even if other agents won't install, ab works right away

      Install without internet access

      Verified offline packs bring the complete runtime with you

      -

      v1.2.0 initially provides slim Codex packs for Linux x64 and Windows x64. CI installs each pack, runs its version command, and uninstalls it.

      +

      v1.3.0 provides slim Codex and OpenCode packs. CI verifies install, version launch, and uninstall on Linux, Windows, Intel macOS, and Apple Silicon.

      • Verified slim packs: download from Releases and carry them to the target machine.
      • Custom offline packs: use menu [7] or build-offline on the target platform.
      • diff --git a/pages/index.html b/pages/index.html index 2e01173..ef56907 100644 --- a/pages/index.html +++ b/pages/index.html @@ -6,13 +6,13 @@ AgentBoot · 一条命令,启动你的 AI Agent - + - - + + @@ -33,7 +33,7 @@
        -

        v1.2.0 · 免费开源 · 三平台可用

        +

        v1.3.0 · 免费开源 · 三平台可用

        一条命令,
        Agent 就位

        把安装、模型、镜像、离线包和安全卸载收进一个清楚的菜单。你选择要用的 AI Agent,AgentBoot 负责把它可靠地装好。

        @@ -47,7 +47,7 @@

        一条命令,
        Agent 就位

          -
        • 14 个 Agent 按需选择
        • +
        • 15 个 Agent 按需选择
        • Linux · macOS · Windows
        • 离线安装
        • MIT License
        • @@ -137,7 +137,7 @@

          即使其他 Agent 装不上,ab 也能马上工作

        -
      1. 01

        按需安装,不装全家桶

        14 个主流 Agent 自由选择:Claude Code、Codex、Qwen Code、OpenCode、MiMo、Cline、Pi、CoCo 等。

      2. +
      3. 01

        按需安装,不装全家桶

        15 个主流 Agent 自由选择:Cursor、Claude Code、Codex、Qwen Code、OpenCode、MiMo、Cline、Pi、CoCo 等。

      4. 02

        模型提供商统一管理

        Agnes 零配置可用,也支持命名管理任意 OpenAI 兼容接口、Ollama、LM Studio 与故障切换顺序。

      5. 03

        中国网络自动适配

        自动选择 npm、Node、PyPI 镜像,Worker、Pages、Release 三源容错,并提供一键代理配置。

      6. 04

        离线包经过真实验证

        发布包完成安装、启动、卸载冒烟;菜单 [7] 还能在目标平台按需构建自定义包。

      7. @@ -150,13 +150,13 @@

        即使其他 Agent 装不上,ab 也能马上工作

        -

        你的工具箱

        14 个 Agent,一个入口

        +

        你的工具箱

        15 个 Agent,一个入口

        在线安装按需选择;标记为离线可用的 Agent 可连同依赖与运行时一起打包。

        -
        14个内置选项
        +
        15个内置选项
        绿点表示支持离线安装
        @@ -164,7 +164,7 @@

        即使其他 Agent 装不上,ab 也能马上工作

        #Agent命令厂商离线 01CoCo AgentcocoBitCookLinux/macOS - 02OpenCodeopencodeopencode.ai仅在线 + 02OpenCodeopencodeopencode.ai支持 03Hermes AgenthermesHermes需 Git 04Cline CLIclineCline支持 05CodeBuddy CLIcodebuddyTencent支持 @@ -177,6 +177,7 @@

        即使其他 Agent 装不上,ab 也能马上工作

        12Gemini CLIgeminiGoogle支持 13iFlow CLIiflowiFlow 心流支持 14AideraiderAider AI仅在线 + 15CursorcursorAnysphere仅在线
        @@ -190,7 +191,7 @@

        即使其他 Agent 装不上,ab 也能马上工作

        没有网络,也能安装

        可验证离线包,把完整运行环境一起带走

        -

        v1.2.0 首批提供 Linux x64 与 Windows x64 Codex 精简包,CI 实际执行安装、版本启动与卸载。

        +

        v1.3.0 提供 Codex 与 OpenCode 精简包;CI 在 Linux、Windows、Intel macOS 与 Apple Silicon 上实际验证安装、版本启动与卸载。

        • 已验证精简包:从 Releases 下载后直接带到目标机。
        • 自建离线包:用菜单 [7]build-offline 在目标平台按需构建。
        • diff --git a/pages/install.ps1 b/pages/install.ps1 index d403b90..be14246 100644 --- a/pages/install.ps1 +++ b/pages/install.ps1 @@ -9,7 +9,7 @@ $ProgressPreference = 'SilentlyContinue' try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} $Repo = 'bit-cook/AgentBoot' -$Tag = 'v1.2.0' +$Tag = 'v1.3.0' $ZipName = "agentboot-online-$Tag.zip" $BootBase = 'https://boot.ide.pub' $GH = "https://github.com/$Repo/releases/download/$Tag" diff --git a/pages/install.sh b/pages/install.sh index 1c86fbe..c2abbfb 100755 --- a/pages/install.sh +++ b/pages/install.sh @@ -9,7 +9,7 @@ set -eu REPO="bit-cook/AgentBoot" -TAG="v1.2.0" +TAG="v1.3.0" TARBALL="agentboot-online-${TAG}.tar.gz" BOOT_BASE="https://boot.ide.pub" GH="https://github.com/${REPO}/releases/download/${TAG}" diff --git a/scripts/build-offline.ps1 b/scripts/build-offline.ps1 index 3bafd1c..ee1a45a 100644 --- a/scripts/build-offline.ps1 +++ b/scripts/build-offline.ps1 @@ -116,10 +116,18 @@ if (Test-Path $Stage) { New-Item -ItemType Directory -Path $Stage -Force | Out-Null & python (Join-Path $Root 'scripts\tools\stage_application.py') $Root $Stage if ($LASTEXITCODE -ne 0) { throw '复制应用显式清单失败' } +$registry = Get-Content (Join-Path $Root 'agents\registry.json') -Raw -Encoding UTF8 | ConvertFrom-Json +$want = if ($Agents) { $Agents -split ',' } else { + @($registry.agents | Where-Object { $_.offline -and (-not $_.os -or $_.os -contains 'windows') } | + ForEach-Object { $_.id }) +} +$needsNode = @($registry.agents | Where-Object { + $want -contains $_.id -and $_.method -eq 'npm' -and $_.id -ne 'opencode' +}).Count -gt 0 # ---------- 2. 下载各平台 Node 运行时 ---------- New-Item -ItemType Directory -Path (Join-Path $Stage 'payloads\node') -Force | Out-Null -foreach ($plat in $Platforms -split ',') { +if ($needsNode) { foreach ($plat in $Platforms -split ',') { $m = $Map[$plat.Trim()] if (-not $m) { Write-Err "未知平台:$plat"; exit 1 } Write-Step "Node 运行时 [$plat] …" @@ -142,14 +150,9 @@ foreach ($plat in $Platforms -split ',') { Move-Item $inner.FullName $dest Remove-Item $tmpEx -Recurse -Force Write-Ok "Node [$plat] 就绪" -} +} } # ---------- 3. 逐 Agent × 平台 安装离线载荷 ---------- -$registry = Get-Content (Join-Path $Root 'agents\registry.json') -Raw -Encoding UTF8 | ConvertFrom-Json -$want = if ($Agents) { $Agents -split ',' } else { - @($registry.agents | Where-Object { $_.offline -and (-not $_.os -or $_.os -contains 'windows') } | - ForEach-Object { $_.id }) -} foreach ($a in $registry.agents) { if ($want -notcontains $a.id -or $a.method -ne 'npm') { continue } foreach ($plat in $Platforms -split ',') { diff --git a/scripts/build-offline.sh b/scripts/build-offline.sh index b5d7a8c..a340768 100755 --- a/scripts/build-offline.sh +++ b/scripts/build-offline.sh @@ -94,9 +94,29 @@ rm -rf "$STAGE" mkdir -p "$STAGE" python3 "$ROOT/scripts/tools/stage_application.py" "$ROOT" "$STAGE" +if [ -n "$AGENTS_ENV" ]; then + WANT="$(echo "$AGENTS_ENV" | tr ',' ' ')" +else + WANT="$(python3 - "$ROOT/agents/registry.json" "$HOST_PLAT" <<'PYEOF' +import json,sys +platform_id=sys.argv[2] +os_id={'win-x64':'windows','linux-x64':'linux','linux-arm64':'linux','darwin-x64':'darwin','darwin-arm64':'darwin'}[platform_id] +agents=json.load(open(sys.argv[1]))['agents'] +print(' '.join(a['id'] for a in agents if a.get('offline') and (not a.get('os') or os_id in a['os']))) +PYEOF +)" +fi +NEEDS_NODE="$(python3 - "$ROOT/agents/registry.json" "$WANT" <<'PYEOF' +import json,sys +wanted=set(sys.argv[2].split()) +agents=json.load(open(sys.argv[1]))['agents'] +print('1' if any(a['id'] in wanted and a.get('method')=='npm' and a['id']!='opencode' for a in agents) else '0') +PYEOF +)" + # ---------- 2. 各平台 Node 运行时 ---------- mkdir -p "$STAGE/payloads/node" -for PLAT in $(echo "$PLATFORMS" | tr ',' ' '); do +if [ "$NEEDS_NODE" = 1 ]; then for PLAT in $(echo "$PLATFORMS" | tr ',' ' '); do step "Node 运行时 [$PLAT] …" case "$PLAT" in win-x64) NF="node-$NODE_VERSION-win-x64.zip"; INNER="node-$NODE_VERSION-win-x64" ;; @@ -123,20 +143,9 @@ for PLAT in $(echo "$PLATFORMS" | tr ',' ' '); do chmod +x "$STAGE/payloads/node/$PLAT/bin/"* 2>/dev/null || true ok "Node [$PLAT] 就绪" done +fi # ---------- 3. Agent 载荷(npm --os/--cpu 跨平台拉取) ---------- -if [ -n "$AGENTS_ENV" ]; then - WANT="$(echo "$AGENTS_ENV" | tr ',' ' ')" -else - WANT="$(python3 - "$ROOT/agents/registry.json" "$HOST_PLAT" <<'PYEOF' -import json,sys -platform_id=sys.argv[2] -os_id={'win-x64':'windows','linux-x64':'linux','linux-arm64':'linux','darwin-x64':'darwin','darwin-arm64':'darwin'}[platform_id] -agents=json.load(open(sys.argv[1]))['agents'] -print(' '.join(a['id'] for a in agents if a.get('offline') and (not a.get('os') or os_id in a['os']))) -PYEOF -)" -fi step "Agent 载荷:$WANT" for AID in $WANT; do PKG="$(python3 - "$ROOT/agents/registry.json" "$AID" <<'PYEOF' diff --git a/scripts/install.ps1 b/scripts/install.ps1 index d403b90..be14246 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -9,7 +9,7 @@ $ProgressPreference = 'SilentlyContinue' try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} $Repo = 'bit-cook/AgentBoot' -$Tag = 'v1.2.0' +$Tag = 'v1.3.0' $ZipName = "agentboot-online-$Tag.zip" $BootBase = 'https://boot.ide.pub' $GH = "https://github.com/$Repo/releases/download/$Tag" diff --git a/scripts/tools/validate_offline_payload.py b/scripts/tools/validate_offline_payload.py index 9c5a017..02a7413 100644 --- a/scripts/tools/validate_offline_payload.py +++ b/scripts/tools/validate_offline_payload.py @@ -35,6 +35,15 @@ def check_npm(stage, agent, platform_id): return "missing npm bin entry for %s on %s" % (agent["id"], platform_id) if agent["id"] == "hermes" and not (root / "PACK_ROOT.txt").is_file(): return "Hermes runtime was not completed on %s" % platform_id + if agent["id"] == "opencode": + binary = "opencode.exe" if platform_id.startswith("win-") else "opencode" + for package_name in (agent.get("offline_binary_packages") or {}).get(platform_id, []): + candidates = ( + root / "node_modules" / "opencode-ai" / "node_modules" / package_name / "bin" / binary, + root / "node_modules" / package_name / "bin" / binary, + ) + if not any(path.is_file() and path.stat().st_size > 10 * 1024 * 1024 for path in candidates): + return "missing OpenCode native package %s on %s" % (package_name, platform_id) return None @@ -49,7 +58,7 @@ def main(): "darwin-x64": "darwin", "darwin-arm64": "darwin"} for platform_id in platforms: node_dir = stage / "payloads" / "node" / platform_id - if any(agents.get(aid, {}).get("method") == "npm" for aid in requested) and not node_dir.is_dir(): + if any(agents.get(aid, {}).get("method") == "npm" and aid != "opencode" for aid in requested) and not node_dir.is_dir(): errors.append("missing Node runtime: %s" % platform_id) for aid in requested: agent = agents.get(aid) diff --git a/tests/test_agent_lifecycle_methods.py b/tests/test_agent_lifecycle_methods.py index d12179e..750b92e 100644 --- a/tests/test_agent_lifecycle_methods.py +++ b/tests/test_agent_lifecycle_methods.py @@ -48,10 +48,30 @@ def test_npm_batch_reuses_mirror_and_environment_detection(self): mirror.assert_called_once_with() child_env.assert_called_once_with() - def test_opencode_not_advertised_offline_until_postinstall_is_supported(self): + def test_opencode_offline_requires_native_regular_and_baseline_packages(self): registry = json.loads((ROOT / "agents/registry.json").read_text(encoding="utf-8")) opencode = next(agent for agent in registry["agents"] if agent["id"] == "opencode") - self.assertFalse(opencode["offline"]) + self.assertTrue(opencode["offline"]) + self.assertEqual(opencode["npm"], "opencode-ai@1.18.25") + self.assertEqual(opencode["offline_binary_packages"]["linux-x64"], + ["opencode-linux-x64", "opencode-linux-x64-baseline"]) + + def test_opencode_selects_first_native_binary_that_runs(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + regular = root / "node_modules/opencode-ai/node_modules/opencode-linux-x64/bin/opencode" + baseline = root / "node_modules/opencode-ai/node_modules/opencode-linux-x64-baseline/bin/opencode" + for path in (regular, baseline): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"x" * (10 * 1024 * 1024 + 1)) + agent = {"offline_binary_packages": {"linux-x64": + ["opencode-linux-x64", "opencode-linux-x64-baseline"]}} + failed = subprocess.CompletedProcess([], 1) + passed = subprocess.CompletedProcess([], 0) + with mock.patch.object(menu.subprocess, "run", side_effect=[failed, passed]) as run: + selected = menu.select_opencode_binary(agent, str(root), "linux-x64") + self.assertEqual(selected, str(baseline)) + self.assertEqual(run.call_count, 2) def test_aider_uses_private_venv_lifecycle(self): registry = json.loads((ROOT / "agents/registry.json").read_text(encoding="utf-8")) diff --git a/tests/test_cursor_lifecycle.py b/tests/test_cursor_lifecycle.py new file mode 100644 index 0000000..656c1b6 --- /dev/null +++ b/tests/test_cursor_lifecycle.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""Cursor official desktop install ownership and safety regressions.""" + +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest +from unittest import mock + + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "core")) + +import menu # noqa: E402 + + +class CursorLifecycleTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.home = Path(self.tmp.name) + self.ab_home = self.home / ".agentboot" + self.apps = self.ab_home / "apps" + self.patches = [ + mock.patch.object(menu, "AB_HOME", str(self.ab_home)), + mock.patch.object(menu, "APPS_DIR", str(self.apps)), + mock.patch.dict(os.environ, {"HOME": str(self.home)}, clear=False), + ] + for patch in self.patches: + patch.start() + + def tearDown(self): + for patch in reversed(self.patches): + patch.stop() + self.tmp.cleanup() + + def test_registry_uses_cursor_special_installer(self): + registry = json.loads((ROOT / "agents" / "registry.json").read_text(encoding="utf-8")) + cursor = next(agent for agent in registry["agents"] if agent["id"] == "cursor") + self.assertEqual((cursor["method"], cursor["special_install"]), ("cursor", "cursor")) + self.assertFalse(cursor["offline"]) + + def test_linux_cursor_install_and_remove_stay_inside_agentboot(self): + download = self.home / "Cursor.AppImage" + download.write_bytes(b"\x7fELF" + b"x" * 128) + completed = subprocess.CompletedProcess([], 0) + with mock.patch.object(menu, "plat_id", return_value="linux-x64"), \ + mock.patch.dict(menu.CURSOR_SHA256, {"linux-x64": "a" * 64}), \ + mock.patch.object(menu, "_download_cursor_asset", + return_value=(str(download), "https://downloads.cursor.com/production/x/Cursor.AppImage", "a" * 64)), \ + mock.patch.object(menu.subprocess, "run", return_value=completed): + self.assertTrue(menu.install_cursor({"id": "cursor"})) + root = self.apps / "cursor" + self.assertTrue((root / "Cursor.AppImage").is_file()) + marker = json.loads((root / "agentboot-managed.json").read_text(encoding="utf-8")) + self.assertEqual(marker["managed_by"], "AgentBoot") + self.assertIn("AgentBoot Cursor launcher", (self.ab_home / "bin" / "cursor").read_text()) + menu._remove_cursor_install() + self.assertFalse(root.exists()) + + def test_cursor_remove_refuses_unmarked_directory(self): + root = self.apps / "cursor" + root.mkdir(parents=True) + (root / "keep.txt").write_text("user data", encoding="utf-8") + with self.assertRaisesRegex(OSError, "归属标记"): + menu._remove_cursor_install() + self.assertTrue((root / "keep.txt").exists()) + + def test_cursor_upgrade_refuses_unmarked_directory(self): + root = self.apps / "cursor" + root.mkdir(parents=True) + with mock.patch.object(menu, "plat_id", return_value="linux-x64"), \ + mock.patch.object(menu, "_download_cursor_asset") as download: + self.assertFalse(menu.install_cursor({"id": "cursor"})) + download.assert_not_called() + + def test_cursor_install_refuses_symlinked_managed_root(self): + target = self.home / "outside" + target.mkdir() + self.apps.mkdir(parents=True) + (self.apps / "cursor").symlink_to(target, target_is_directory=True) + with mock.patch.object(menu, "plat_id", return_value="linux-x64"), \ + mock.patch.object(menu, "_download_cursor_asset") as download: + self.assertFalse(menu.install_cursor({"id": "cursor"})) + download.assert_not_called() + self.assertTrue(target.exists()) + + def test_cursor_download_rejects_nonofficial_redirect(self): + class Response: + headers = {"Content-Length": "2000000"} + def __enter__(self): return self + def __exit__(self, *_args): return False + def geturl(self): return "https://evil.example/Cursor.AppImage" + def read(self, _size): return b"" + with mock.patch("urllib.request.urlopen", return_value=Response()), \ + self.assertRaisesRegex(ValueError, "非官方"): + menu._download_cursor_asset(menu.CURSOR_DOWNLOADS["linux-x64"], ".AppImage") + + def test_cursor_platform_commands_preserve_signature_checks(self): + source = (ROOT / "core" / "menu.py").read_text(encoding="utf-8") + self.assertIn("Get-AuthenticodeSignature", source) + self.assertIn('"codesign", "--verify", "--deep", "--strict"', source) + self.assertIn('"spctl", "--assess", "--type", "execute"', source) + self.assertIn('"hdiutil", "attach", "-readonly"', source) + + def test_cursor_marker_recovers_managed_detection_without_state(self): + root = self.apps / "cursor" + root.mkdir(parents=True) + (root / "agentboot-managed.json").write_text( + json.dumps({"managed_by": "AgentBoot"}), encoding="utf-8") + status = menu.detect_install({"id": "cursor", "name": "Cursor", "bin": "cursor", + "method": "cursor"}) + self.assertTrue(status["managed"]) + self.assertEqual(status["entry"]["method"], "cursor") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_docs_consistency.py b/tests/test_docs_consistency.py index f690335..5e5d811 100644 --- a/tests/test_docs_consistency.py +++ b/tests/test_docs_consistency.py @@ -2,6 +2,7 @@ """Keep duplicated user-facing release facts synchronized.""" from pathlib import Path +import json import unittest @@ -12,7 +13,7 @@ class DocumentationConsistencyTests(unittest.TestCase): def test_chinese_guides_share_release_critical_facts(self): guides = [(ROOT / "安装指南.md").read_text(encoding="utf-8"), (ROOT / "docs/zh/安装指南.md").read_text(encoding="utf-8")] - facts = ("v1.2.0", "win-x64-codex.zip", "linux-x64-codex.tar.gz", + facts = ("v1.3.0", "win-x64-codex.zip", "linux-x64-codex.tar.gz", "PAYLOAD_SHA256SUMS.txt", "agentboot uninstall", "--purge", "Hermes", "目标平台") for fact in facts: @@ -23,7 +24,7 @@ def test_web_surfaces_share_release_critical_facts(self): surfaces = [(ROOT / "pages/index.html").read_text(encoding="utf-8"), (ROOT / "pages/en/index.html").read_text(encoding="utf-8"), (ROOT / "cloudflare/web-assets.js").read_text(encoding="utf-8")] - for fact in ("v1.2.0", "Codex", "uninstall"): + for fact in ("v1.3.0", "Codex", "OpenCode", "Cursor", "uninstall"): for surface in surfaces: self.assertIn(fact, surface, fact) @@ -45,6 +46,15 @@ def test_pages_include_accessible_responsive_interactions(self): self.assertIn("navigator.clipboard", script) self.assertIn("document.execCommand", script) + def test_pages_match_registry_agent_count_and_offline_claims(self): + registry = json.loads((ROOT / "agents/registry.json").read_text(encoding="utf-8")) + self.assertEqual(len(registry["agents"]), 15) + for relative in ("pages/index.html", "pages/en/index.html"): + page = (ROOT / relative).read_text(encoding="utf-8") + self.assertEqual(page.count(''), 15, relative) + self.assertIn("Cursor", page, relative) + self.assertIn("OpenCode", page, relative) + def test_worker_web_bundle_is_generated_and_cacheable(self): worker = (ROOT / "cloudflare/worker.js").read_text(encoding="utf-8") assets = (ROOT / "cloudflare/web-assets.js").read_text(encoding="utf-8") @@ -53,7 +63,7 @@ def test_worker_web_bundle_is_generated_and_cacheable(self): self.assertIn('request.headers.get("If-None-Match")', worker) self.assertIn("status: 304", worker) self.assertIn('return webResponse(request, "/404.html", "no-store", 404)', worker) - for fact in ("v1.2.0", "Codex", "uninstall", "site.css", "site.js"): + for fact in ("v1.3.0", "Codex", "OpenCode", "Cursor", "uninstall", "site.css", "site.js"): self.assertIn(fact, assets) diff --git a/tests/test_offline_payload_validation.py b/tests/test_offline_payload_validation.py index 17c6fcd..7a5b192 100644 --- a/tests/test_offline_payload_validation.py +++ b/tests/test_offline_payload_validation.py @@ -3,6 +3,7 @@ import json from pathlib import Path +import shutil import subprocess import sys import tempfile @@ -15,6 +16,20 @@ class OfflinePayloadValidationTests(unittest.TestCase): + def test_opencode_placeholder_without_native_packages_fails(self): + with tempfile.TemporaryDirectory() as tmp: + stage = Path(tmp) + shutil.copytree(ROOT / "agents", stage / "agents") + package = stage / "payloads/agents/opencode/linux-x64/node_modules/opencode-ai" + (package / "bin").mkdir(parents=True) + metadata = {"name": "opencode-ai", "bin": {"opencode": "bin/opencode.exe"}} + (package / "package.json").write_text(json.dumps(metadata), encoding="utf-8") + (package / "bin/opencode.exe").write_text("placeholder", encoding="utf-8") + result = subprocess.run([sys.executable, str(VALIDATOR), str(stage), + "linux-x64", "opencode"], capture_output=True, text=True) + self.assertNotEqual(result.returncode, 0) + self.assertIn("missing OpenCode native package", result.stderr) + def test_missing_npm_payload_fails(self): with tempfile.TemporaryDirectory() as tmp: stage = Path(tmp) diff --git a/tests/test_release_workflow.py b/tests/test_release_workflow.py index 0b0753c..269c88e 100644 --- a/tests/test_release_workflow.py +++ b/tests/test_release_workflow.py @@ -18,7 +18,12 @@ def test_release_supports_prerelease_dry_run_and_tag_only_publish(self): self.assertIn("--prerelease", text) self.assertIn("verify-live-release.py", text) self.assertIn("--prerelease=false --latest", text) - self.assertIn("needs: [validate, online, offline-linux, offline-windows, macos-smoke]", text) + self.assertIn("extended-linux", text) + self.assertIn("extended-windows", text) + self.assertIn("extended-macos-x64", text) + self.assertIn("extended-macos-arm64", text) + self.assertIn("AGENTS=opencode", text) + self.assertIn("python3 core/menu.py install cursor", text) self.assertIn("$smokeHome", text) self.assertIn("测试-home", text) self.assertNotIn("$home =", text) diff --git a/tests/test_runtime_platforms.py b/tests/test_runtime_platforms.py index b1d446a..f8ee788 100644 --- a/tests/test_runtime_platforms.py +++ b/tests/test_runtime_platforms.py @@ -66,6 +66,11 @@ def test_unknown_architecture_is_rejected(self): class OfflineBuilderTests(unittest.TestCase): + def test_opencode_only_build_skips_node_runtime(self): + shell = (ROOT / "scripts" / "build-offline.sh").read_text(encoding="utf-8") + powershell = (ROOT / "scripts" / "build-offline.ps1").read_text(encoding="utf-8-sig") + self.assertIn("a['id']!='opencode'", shell) + self.assertIn("$_.id -ne 'opencode'", powershell) def test_default_build_targets_native_platform_only(self): shell = (ROOT / "scripts/build-offline.sh").read_text(encoding="utf-8") powershell = (ROOT / "scripts/build-offline.ps1").read_text(encoding="utf-8-sig") diff --git "a/\345\256\211\350\243\205\346\214\207\345\215\227.md" "b/\345\256\211\350\243\205\346\214\207\345\215\227.md" index ab6d88a..93187f2 100644 --- "a/\345\256\211\350\243\205\346\214\207\345\215\227.md" +++ "b/\345\256\211\350\243\205\346\214\207\345\215\227.md" @@ -1,8 +1,8 @@ # AgentBoot 安装指南 -> **版本 v1.2.0** · 支持 Linux / macOS / Windows · 界面默认中文(切换:`agentboot lang en`) +> **版本 v1.3.0** · 支持 Linux / macOS / Windows · 界面默认中文(切换:`agentboot lang en`) > 语言 / Language: 中文(本页) | [English](docs/en/install-guide.md) | 中文副本:[docs/zh/安装指南.md](docs/zh/安装指南.md) -> AgentBoot 是一个极简、极速、开箱即用的 AI Agent 启动器:内置一个保底最小 Agent(默认 Agnes 免费模型),其余 Agent(CoCo Agent、OpenCode、Hermes Agent、Cline、CodeBuddy、Pi、Claude Code、Codex、Qwen Code、MiMo Code、OpenClaw、Gemini CLI、iFlow CLI、Aider)通过**菜单自选安装**。除 Aider(pip 生态)外,**全部 Agent 支持离线安装**(离线包内置完整运行时,含 Hermes 的 Python venv 与 CoCo 的私有 Node)。 +> AgentBoot 是一个极简、极速、开箱即用的 AI Agent 启动器:内置保底 Agent,其余 Agent(含 Cursor、OpenCode、Claude Code、Codex 等)通过菜单自选安装。离线能力以注册表和目标平台构建验证为准;Cursor 与 Aider 当前仅在线安装,OpenCode 使用固定官方 native binary 离线包,Hermes 必须在目标平台原生构建。 --- @@ -61,9 +61,9 @@ curl -fsSL https://bit-cook.github.io/AgentBoot/install.sh | sh - **方式 A(推荐)**:到 GitHub Releases 下载现成的离线包 `https://github.com/bit-cook/AgentBoot/releases` → 选择 - - `AgentBoot-offline-v1.2.0-win-x64-codex.zip`(Windows x64) - - `AgentBoot-offline-v1.2.0-linux-x64-codex.tar.gz`(Linux x64) - - `AgentBoot-offline-v1.2.0-linux-x64-codex-sfx.sh`(Linux x64 自解压单文件) + - `AgentBoot-offline-v1.3.0-win-x64-codex.zip`(Windows x64) + - `AgentBoot-offline-v1.3.0-linux-x64-codex.tar.gz`(Linux x64) + - `AgentBoot-offline-v1.3.0-linux-x64-codex-sfx.sh`(Linux x64 自解压单文件) - **方式 B(自建)**:在有网的 Linux/macOS 或 Windows 机器上运行项目自带的构建脚本, 自动从 npmmirror 镜像拉取全部 Agent 离线载荷并打包: @@ -83,9 +83,9 @@ curl -fsSL https://bit-cook.github.io/AgentBoot/install.sh | sh | 目标机器情况 | 解压方法 | |---|---| | **Windows(任意版本)** | 直接**右键 ZIP → 全部解压缩**。Windows 资源管理器自带 ZIP 支持,无需安装任何软件 | -| **Windows 10/11(命令行党)** | 系统自带 `tar`:`tar -xf AgentBoot-offline-v1.2.0-win-x64-codex.zip` | -| **Linux x64** | 系统自带 `tar`:`tar -xzf AgentBoot-offline-v1.2.0-linux-x64-codex.tar.gz -C ~` | -| **Linux x64 极简环境** | 用自解压单文件:`sh AgentBoot-offline-v1.2.0-linux-x64-codex-sfx.sh` | +| **Windows 10/11(命令行党)** | 系统自带 `tar`:`tar -xf AgentBoot-offline-v1.3.0-win-x64-codex.zip` | +| **Linux x64** | 系统自带 `tar`:`tar -xzf AgentBoot-offline-v1.3.0-linux-x64-codex.tar.gz -C ~` | +| **Linux x64 极简环境** | 用自解压单文件:`sh AgentBoot-offline-v1.3.0-linux-x64-codex-sfx.sh` | > 自解压包 `*-sfx.sh` 本身就是一个 shell 脚本 + 内嵌 base64 数据,`sh xxx-sfx.sh` 即自动解压并直接进入安装流程,非常适合只有最小化系统的服务器。 @@ -183,7 +183,7 @@ python core/menu.py add-agent --del myagent ## 📦 自定义离线安装包(按需瘦身) -官方 v1.1 Release 提供经过安装/启动/卸载验证的 Codex 精简包;其他 Agent 请在目标平台自行构建按需包: +官方 v1.3 Release 提供经过安装/启动/卸载验证的 Codex 与 OpenCode 精简包;其他 Agent 请在目标平台自行构建按需包: **方式一:菜单向导(推荐)**