diff --git a/wechat_plain/scripts/local-testkit/docs/HERMES_AGENT_PIXEL_SETUP.md b/wechat_plain/scripts/local-testkit/docs/HERMES_AGENT_PIXEL_SETUP.md new file mode 100644 index 0000000..bc751e5 --- /dev/null +++ b/wechat_plain/scripts/local-testkit/docs/HERMES_AGENT_PIXEL_SETUP.md @@ -0,0 +1,159 @@ +# 在 Pixel 上编译安装 hermes-agent + 配置 Kimi Coding Plan + +> 目标设备:Pixel 8 Pro (husky),Android 16,已 root(Magisk)+ Termux。 +> hermes-agent = **NousResearch 的自改进 AI agent**(github.com/NousResearch/hermes-agent,Python 3.11+/Node),提供 `hermes` CLI(`hermes` 聊天 / `hermes gateway` 消息网关 / `hermes setup` 配置)。本文记录从 Mac 经 adb 把它装到 Pixel Termux,并接 Kimi Coding Plan 作为 LLM 后端的完整流程。 +> +> 相关记忆:`~/.claude/.../memory/hermes-agent-termux-install.md`、`magisk-su-android-inet-group.md`、`termux-path-convention.md`。 + +--- + +## 0. 前置 + +- 设备已 root,Termux 已装(GitHub/F-Droid 版,非 Play 商店)。Termux 重装/设备 restore 后 **uid 会变**,先取当前 uid: + ```bash + adb shell 'su -c "stat -c %u /data/data/com.termux"' # 例:10302(low3 = 302) + ``` +- 所有「以 Termux 用户身份联网」的命令必须带 inet 等 supplementary group,否则 DNS/网络全断。统一模板(uid=10302 为例): + ``` + su -g 10302 -G 3003 -G 9997 -G 20302 -G 50302 10302 -c "" + ``` + 其中 `3003=inet 9997=everybody 20=cache 50=all_aN`。详见 memory `magisk-su-android-inet-group`。 +- Termux 环境变量(脚本里统一 export): + ```bash + export PREFIX=/data/data/com.termux/files/usr + export HOME=/data/data/com.termux/files/home + export PATH=$PREFIX/bin:$PATH + export LD_LIBRARY_PATH=$PREFIX/lib + export TMPDIR=$PREFIX/tmp + export TERMUX_VERSION=0.118.3 + ``` + +--- + +## 1. 编译安装 hermes-agent + +官方安装器原生支持 Termux:检测到 Termux 后用 stdlib venv+pip(不用 uv),自动 `pkg install` python/git/nodejs/clang/rust/ffmpeg/ripgrep,从源码编译 native wheel(maturin / pydantic-core / jiter…),装到 `~/.hermes/hermes-agent/venv`,并在 `$PREFIX/bin/hermes` 建 wrapper。**整机编译约 25–30 分钟**。 + +### 1.1 ⚠️ 必做:先升级 Termux 全量包(否则 node 坏) + +全新 bootstrap 过期时,`pkg install nodejs` 装上的 node 链不上旧 openssl: +``` +node: CANNOT LINK EXECUTABLE "node": cannot locate symbol "OSSL_PROVIDER_add_conf_parameter" +``` +install.sh 因 `set -e` 在 Node 步骤 `rc=1` 退出。**先升级再装**: +```bash +export DEBIAN_FRONTEND=noninteractive +apt-get update -y +apt-get upgrade -y -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef +node --version # 应能正常打印版本(不再 CANNOT LINK) +``` + +### 1.2 跑安装器(脱离 + 日志,扛 adb 掉线/doze) + +把下面脚本 push 到 `/sdcard`,再以 Termux 用户 `setsid nohup` 脱离运行: + +```bash +#!/data/data/com.termux/files/usr/bin/bash +# 见上方 export 块…… +export DEBIAN_FRONTEND=noninteractive +cd "$HOME"; termux-wake-lock 2>/dev/null || true +echo "=== UPGRADE START $(date) ===" +apt-get update -y; apt-get upgrade -y -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef +node --version || { echo "NODE BROKEN"; exit 2; } +echo "=== HERMES INSTALL $(date) ===" +curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --non-interactive +echo "=== HERMES INSTALL END rc=$? $(date) ===" +``` +```bash +adb push install.sh /sdcard/install.sh +LOG=/data/data/com.termux/files/home/hermes-install.log +adb shell "su -g 10302 -G 3003 -G 9997 -G 20302 -G 50302 10302 -c \ + 'setsid nohup /data/data/com.termux/files/usr/bin/bash /sdcard/install.sh >$LOG 2>&1 注意:`hermes update` 会重新生成 wrapper、把 shebang 改回 `/usr/bin/env`,**每次更新后需重修**。 +> `venv/bin/hermes` 的 shebang 是绝对 venv python,本就没问题,可用它绕过 wrapper 验证。 + +### 1.4 验证 + +```bash +adb shell 'su -g 10302 -G 3003 -G 9997 -G 20302 -G 50302 10302 -c " + export PREFIX=/data/data/com.termux/files/usr HOME=/data/data/com.termux/files/home + export PATH=\$PREFIX/bin:\$PATH LD_LIBRARY_PATH=\$PREFIX/lib TMPDIR=\$PREFIX/tmp + hermes --version"' +# → Hermes Agent v0.15.1 (2026.5.29) · Python 3.13.13 +``` +产物位置:代码 `~/.hermes/hermes-agent/`,配置 `~/.hermes/config.yaml` + `~/.hermes/.env`,skills `~/.hermes/skills/`。 + +--- + +## 2. 配置 Kimi Coding Plan(sk-kimi- key) + +hermes **原生支持** Kimi Coding Plan,**不要**用 `provider=anthropic`+`ANTHROPIC_BASE_URL` 绕路。 + +### 2.1 源码逻辑(为什么这么配) + +| 文件 | 逻辑 | +|---|---| +| `hermes_cli/auth.py:248` | `kimi-coding` provider:`api_key_env_vars=("KIMI_API_KEY","KIMI_CODING_API_KEY")` | +| `hermes_cli/auth.py:515` | `KIMI_CODE_BASE_URL = "https://api.kimi.com/coding"`;`_resolve_kimi_base_url()` 把 `sk-kimi-` 前缀 key 自动重定向到这里 | +| `agent/anthropic_adapter.py:385` | `_is_kimi_coding_endpoint(base_url)` 检测到 `api.kimi.com/coding` → **自动切 Anthropic Messages 协议 + 注入 claude-code User-Agent** | +| `hermes_cli/main.py` `_model_flow_kimi()` | setup 向导:写 `model.provider/base_url/default`,清空 `KIMI_BASE_URL` override | + +**关键坑**:Kimi `/coding` 端点的 OpenAI 路径 `/coding/v1/chat/completions` 返回 **403** `access_terminated_error`("only available for Coding Agents such as Kimi CLI, Claude Code, Roo Code…");只有 Anthropic 路径 `/coding/v1/messages` 通(200)。hermes 通过上面的 UA 伪装自动绕过,所以**必须走 kimi-coding provider(anthropic 协议),不能用 OpenAI 协议**。 + +### 2.2 落地配置(等价于 `hermes model` 选 Kimi) + +**`~/.hermes/.env`**:只放 key,**不要**设 `KIMI_BASE_URL`,也别留 `ANTHROPIC_*`(否则 `provider: auto` 会误判): +``` +KIMI_API_KEY=sk-kimi-XXXXXXXX... +``` +> 用 `cat /sdcard/x >> .env`(root 追加不改属主)或 `su -c 'sed ...'`(Termux 用户改,保属主)避免把 key 写进命令行/ps。 + +**`~/.hermes/config.yaml`**(用 CLI 设,非交互): +```bash +hermes config set model.provider kimi-coding +hermes config set model.base_url https://api.kimi.com/coding +hermes config set model.default kimi-for-coding +``` +可用模型:`kimi-k2.6 / kimi-k2.5 / kimi-for-coding / kimi-k2-thinking / kimi-k2-thinking-turbo`。 + +### 2.3 验证 + +```bash +# -z/--oneshot = 一次性 prompt,免 TTY +hermes -z "用中文只回答一个数字: 1+1=?" +# → 2 +``` +裸 `hermes`(不带 `-m/--provider`)直接走 Kimi 即配置成功。 + +### 2.4 排错速查 + +| 现象 | 原因 / 处理 | +|---|---| +| `HTTP 404 resource not found` | base_url 漏了协议路径或用了 `provider=anthropic`+错 base;改用 `kimi-coding` + `https://api.kimi.com/coding` | +| `HTTP 403 access_terminated_error` | 走了 OpenAI `/chat/completions` 路径;必须 kimi-coding(anthropic 协议,hermes 自动加 claude-code UA) | +| `no final response was produced` | config 残留错的 `model.base_url`(如 openrouter)或 provider 不对;按 2.2 重设三项 | +| `curl: Could not resolve host` | `su` 切 uid 时漏了 `-G 3003`(inet 组) | + +--- + +## 附:一次性自检脚本 + +```bash +adb shell 'su -g 10302 -G 3003 -G 9997 -G 20302 -G 50302 10302 -c " + export PREFIX=/data/data/com.termux/files/usr HOME=/data/data/com.termux/files/home + export PATH=\$PREFIX/bin:\$PATH LD_LIBRARY_PATH=\$PREFIX/lib TMPDIR=\$PREFIX/tmp + hermes --version + hermes config show | grep -iE \"provider|default|base_url\" + hermes -z \"ping, reply PONG\" +"' +``` diff --git a/wechat_plain/scripts/local-testkit/docs/PIXEL_CLAUDE_CODE_OAUTH_SETUP.md b/wechat_plain/scripts/local-testkit/docs/PIXEL_CLAUDE_CODE_OAUTH_SETUP.md new file mode 100644 index 0000000..2be327e --- /dev/null +++ b/wechat_plain/scripts/local-testkit/docs/PIXEL_CLAUDE_CODE_OAUTH_SETUP.md @@ -0,0 +1,281 @@ +# Pixel + Termux 复用 Mac OAuth Token 跑 Claude Code + +> 在 Pixel 10 Pro Fold(Android 16)的 Termux + Ubuntu proot 容器里运行 `claude-run`,复用 Mac 上同款 Anthropic OAuth Token,**完全替换原来的 Kimi 反代方案**。 + +--- + +## 1. 背景 + +- **设备**:Pixel 10 Pro Fold(代号 `rango`)/ Android 16,bootloader 已解锁,Magisk root +- **原方案**:claude-code 通过 Kimi Coding Plan 反代(`https://api.kimi.com/coding/`),用 Kimi token 走所有请求 +- **新目标**:直连 `api.anthropic.com`,复用 Mac 上 `~/.zshrc` 里的 `CLAUDE_CODE_OAUTH_TOKEN`,使用真正的 Claude Opus 4.7 +- **动机**:Frida 在 Android 16 无法跑(ptrace/CFI/SELinux 多重限制),后续工作转向 LSPosed + 真 Claude 模型,需要 Pixel 端本地也能跑 claude-code + +--- + +## 2. 架构总览 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Termux (Android shell, user u0_a327) │ +│ │ +│ ~/.bashrc │ +│ └─ export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-... │ +│ │ +│ /usr/bin/claude-run ←─ wrapper │ +│ └─ proot-distro login --shared-home ubuntu │ +│ -- env CLAUDE_CODE_OAUTH_TOKEN="..." claude "$@" │ +│ │ +│ ▼ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Ubuntu proot container │ │ +│ │ /root ←(--shared-home)─→ /data/data/com.termux/.../home│ +│ │ ├─ .claude.json ← claude 主配置 │ │ +│ │ └─ .claude/settings.json ← per-user settings │ │ +│ │ │ │ +│ │ claude (ELF/glibc, Node.js) │ │ +│ │ └─→ HTTPS api.anthropic.com │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**关键点**: +- Claude Code 是 ELF/glibc 编译,Android Bionic 跑不了 → 必须 proot 跑 Ubuntu +- `--shared-home` 把 Termux home 绑定为容器 `/root`,配置文件可以从 Termux 端直接编辑 +- Token 通过 `env KEY=VAL claude` 在容器内瞬时注入,不进容器 shell 环境 + +--- + +## 3. Claude Code 的双重认证闸门 + +这是整个迁移最关键的认知 —— claude-code 启动时有两道**独立**闸门: + +| 闸门 | 检查内容 | headless (`claude -p`) | 交互模式 (`claude`) | +|------|---------|----------------------|---------------------| +| ① Token 闸门 | `CLAUDE_CODE_OAUTH_TOKEN` env 或 `~/.claude/.credentials.json` 任一存在 | 必须过 | 必须过 | +| ② Onboarding 闸门 | `~/.claude.json` 的 `hasCompletedOnboarding === true` | **跳过** | **必须过** | + +**Kimi 反代为什么"屏蔽"了 Onboarding 闸门**:它的 `.env` 块在启动时被 claude-code 注入进进程,等价于一直在用 API key 模式,OAuth onboarding 检查被旁路掉了。一旦切回官方 Anthropic,闸门被激活,于是裸 `claude-run` 弹出 OAuth URL。 + +--- + +## 4. 完整改动清单 + +按依赖顺序(缺一不可): + +### 4.1 改 wrapper:注入 OAuth Token + +**文件**:`/data/data/com.termux/files/usr/bin/claude-run` + +```sh +#!/data/data/com.termux/files/usr/bin/sh +# Claude Code via proot-distro with shared home + Mac OAuth token +exec proot-distro login --shared-home ubuntu -- env CLAUDE_CODE_OAUTH_TOKEN="sk-ant-oat01-<...>" claude "$@" +``` + +权限:`chmod 711`(owner 可读,其他 app 不可读),owner `u0_a327:u0_a327`。 + +> Token 直接 inline 到 wrapper,避免依赖 shell 启动文件。Android 沙箱保证其他 app 读不到 `/data/data/com.termux/` 下的文件。 + +### 4.2 写 settings.json:清理 Kimi 痕迹 + +**文件**:`/data/data/com.termux/files/home/.claude/settings.json` + +```json +{ + "env": {}, + "model": "opus", + "skipDangerousModePermissionPrompt": true, + "theme": "dark" +} +``` + +权限:`chmod 600`。 + +> **`env` 必须为空 `{}`** —— 之前 Kimi 配置在这里塞了 `ANTHROPIC_BASE_URL` / `ANTHROPIC_API_KEY` 等,会覆盖 wrapper 注入的 token。 + +### 4.3 清理 `~/.claude.json` 的 env 块(最容易漏的一步) + +**文件**:`/data/data/com.termux/files/home/.claude.json` + +claude-code 的"主配置文件",顶层有个 `env` 字段会在进程启动时被 setenv 进去,**优先级高于 wrapper 传入的 env**。 + +操作(用 jq 在 Mac 端处理后 push 回去): + +```bash +jq 'del(.env) + | .customApiKeyResponses.rejected = [] + | .model = "opus" + | .hasCompletedOnboarding = true + | .lastOnboardingVersion = "2.1.144"' \ + ~/.claude.json.backup > ~/.claude.json.cleaned +``` + +需要保证: +- `.env` 字段被**删除**(不是设为空,是整个 key 不存在) +- `.hasCompletedOnboarding = true` ← **过 Onboarding 闸门** +- `.lastOnboardingVersion = "2.1.144"` ← 与当前 claude-code 版本对齐 +- `.model = "opus"`(之前 Kimi 用的 `"opus[1m]"` 在官方 API 不存在) +- `.customApiKeyResponses.rejected = []`(清掉之前 reject 过的 Kimi key 历史) + +权限:`chmod 600`,owner `u0_a327:u0_a327`。 + +--- + +## 5. 完整执行脚本(从 Mac 端一键执行) + +```bash +#!/usr/bin/env bash +set -euo pipefail + +# 1. 从 Mac ~/.zshrc 提取 token +TOKEN=$(awk -F= '/^export CLAUDE_CODE_OAUTH_TOKEN=/{sub(/^export CLAUDE_CODE_OAUTH_TOKEN=/, ""); print; exit}' ~/.zshrc) +[ -z "$TOKEN" ] && { echo "ERROR: token not found in ~/.zshrc"; exit 1; } + +# 2. 构造文件 +mkdir -p /tmp/claude-pixel +cat > /tmp/claude-pixel/claude-run < /tmp/claude-pixel/settings.json <<'EOF' +{ + "env": {}, + "model": "opus", + "skipDangerousModePermissionPrompt": true, + "theme": "dark" +} +EOF + +# 3. 处理 .claude.json(先 pull,patch,push) +adb shell "su -c 'cp /data/data/com.termux/files/home/.claude.json /sdcard/cc.json && chmod 644 /sdcard/cc.json'" +adb pull /sdcard/cc.json /tmp/claude-pixel/cc.json +adb shell "su -c 'rm /sdcard/cc.json'" +jq 'del(.env) + | .customApiKeyResponses.rejected = [] + | .model = "opus" + | .hasCompletedOnboarding = true + | .lastOnboardingVersion = "2.1.144"' \ + /tmp/claude-pixel/cc.json > /tmp/claude-pixel/cc.patched.json + +# 4. push & 安装 +adb push /tmp/claude-pixel/claude-run /sdcard/claude-run.new +adb push /tmp/claude-pixel/settings.json /sdcard/settings.json.new +adb push /tmp/claude-pixel/cc.patched.json /sdcard/cc.json + +adb shell "su -c ' +WRAPPER=/data/data/com.termux/files/usr/bin/claude-run +SETTINGS=/data/data/com.termux/files/home/.claude/settings.json +MAIN=/data/data/com.termux/files/home/.claude.json + +# 备份原版(只备份一次) +[ ! -f \$WRAPPER.prekimi ] && cp \$WRAPPER \$WRAPPER.prekimi +[ ! -f \$MAIN.prekimi ] && cp \$MAIN \$MAIN.prekimi + +# 安装新版 +cp /sdcard/claude-run.new \$WRAPPER +chown u0_a327:u0_a327 \$WRAPPER && chmod 711 \$WRAPPER + +mkdir -p \$(dirname \$SETTINGS) +cp /sdcard/settings.json.new \$SETTINGS +chown u0_a327:u0_a327 \$SETTINGS && chmod 600 \$SETTINGS + +cp /sdcard/cc.json \$MAIN +chown u0_a327:u0_a327 \$MAIN && chmod 600 \$MAIN + +# 清理 +rm /sdcard/claude-run.new /sdcard/settings.json.new /sdcard/cc.json +echo done +'" + +# 5. 清理 Mac 临时文件 +rm -rf /tmp/claude-pixel +``` + +--- + +## 6. 验证 + +### 6.1 headless 模式 + +```bash +adb shell 'su u0_a327 -c " +export HOME=/data/data/com.termux/files/home +export PATH=/data/data/com.termux/files/usr/bin:\$PATH +cd \$HOME +claude-run -p \"reply with just OK\" < /dev/null +"' +``` + +期望输出:`OK`(前面有几行 `proot warning: can't sanitize binding /proc/self/fd/...` 是 adb 非 tty 模式的良性警告,无视)。 + +### 6.2 交互模式(用户在 Termux 里直接跑) + +``` +$ claude-run +``` + +期望:直接进入 Claude Code TUI 界面,**不**弹出 OAuth URL。 + +### 6.3 排错检查清单 + +| 症状 | 检查 | +|------|------| +| 报错指向 `kimi.com/code/console` | `~/.claude.json` 的 `env` 块没删干净 | +| 弹出 OAuth 登录 URL | `~/.claude.json` 的 `hasCompletedOnboarding` 不是 `true` | +| `Failed to authenticate. 401` | Mac 那边的 token 已失效,去 Mac 重新登录刷新 | +| `Warning: no stdin data received in 3s` | headless 模式没加 `< /dev/null`,等 3 秒后会继续,不是错误 | +| `which claude-run` 指向其他路径 | PATH 优先级问题 / 装了 alias | + +--- + +## 7. 回滚 + +之前的版本都备份在 `.prekimi` 后缀文件里: + +```bash +adb shell "su -c ' +cp /data/data/com.termux/files/usr/bin/claude-run.prekimi /data/data/com.termux/files/usr/bin/claude-run +cp /data/data/com.termux/files/home/.claude.json.prekimi /data/data/com.termux/files/home/.claude.json +echo rolled back to Kimi config +'" +``` + +--- + +## 8. 安全注意 + +- Wrapper 里 inline 的 OAuth token 是个**长效凭证**,泄露等于账号被借用 +- 文件权限:wrapper `711`(不可读其他 app),`.claude.json` `600` +- Android 沙箱保证其他 app 无法访问 `/data/data/com.termux/` 下的文件 +- 如果手机丢失或共享,**Mac 端立刻去 Anthropic console 撤销该 token**(一撤销 Mac/Pixel 同时失效,需要重新登录刷新) + +--- + +## 9. 已知限制 + +1. **网络出口**:直连 `api.anthropic.com` 需要稳定国际网络。Pixel 在 4G 蜂窝下不一定通,建议挂代理或走 Wi-Fi +2. **计费归属**:使用 Mac 同一个 Anthropic 账号配额,Pixel 跑多了会消耗 Mac 同账号的 quota +3. **Token 刷新**:OAuth access token 有效期约 1 年,过期后需要在 Mac 端重新执行 OAuth 流程刷新,然后把新 token 同步到 Pixel wrapper +4. **多设备 token 复用合规性**:Anthropic ToS 允许同一账号在多设备使用,但禁止把 token 分享给第三方 + +--- + +## 10. 相关文件索引 + +| 路径 | 作用 | +|------|------| +| `/data/data/com.termux/files/usr/bin/claude-run` | wrapper(inline token) | +| `/data/data/com.termux/files/usr/bin/claude-run.prekimi` | 旧版备份 | +| `/data/data/com.termux/files/home/.claude.json` | claude-code 主配置 | +| `/data/data/com.termux/files/home/.claude.json.prekimi` | 主配置备份(含 Kimi) | +| `/data/data/com.termux/files/home/.claude/settings.json` | per-user settings | +| `/data/data/com.termux/files/usr/var/lib/proot-distro/containers/ubuntu/rootfs` | Ubuntu 容器 rootfs | + +Mac 端: +| 路径 | 作用 | +|------|------| +| `~/.zshrc` 里的 `CLAUDE_CODE_OAUTH_TOKEN` | Token 来源 | +| `~/.claude.json` | Mac 上对应的主配置(参照对比用) | diff --git a/wechat_plain/scripts/local-testkit/docs/README.md b/wechat_plain/scripts/local-testkit/docs/README.md index 2452a89..0a0a2e6 100644 --- a/wechat_plain/scripts/local-testkit/docs/README.md +++ b/wechat_plain/scripts/local-testkit/docs/README.md @@ -302,6 +302,8 @@ local-testkit/ ├── HERMES_WECHAT_ARCHITECTURE.md ├── REVERSE_ENGINEERING_LOG.md ├── NOTIFICATION_STRATEGY.md + ├── PIXEL_CLAUDE_CODE_OAUTH_SETUP.md # Pixel 上 claude-run 复用 Mac OAuth + ├── HERMES_AGENT_PIXEL_SETUP.md # 在 Pixel 编译装 NousResearch hermes-agent + 配 Kimi Coding Plan └── plan.md server/services/wechat/ # 主 plugin(Node.js)