CLI-Anything AdGuardHome Harness:通过 cli-anything-adguardhome 用命令行与 Agent 控制 AdGuardHome
本文以 adguardhome/agent-harness/cli_anything/adguardhome/README.md 为主体,讲解 CLI-Anything 项目中为 AdGuardHome 构建的 CLI harness:从前置依赖、安装与连接配置,到过滤规则、DNS 重写、客户端、统计与日志的完整操作命令,并结合 源码实现 深入 API 客户端层、配置解析优先级与错误处理机制。读完本文,你将能在终端或 Agent 工作流中直接管理一个正在运行的 AdGuardHome 实例,并理解每条命令背后对应的 REST API 调用链。
定位与前置依赖
cli-anything-adguardhome 是 CLI-Anything 项目"Making ALL Software Agent-Native"理念在 AdGuardHome 上的落地:它是一个 CLI harness,让你可以从命令行(或经由 Agent)控制广告拦截器,而不是打开 Web 界面点点点。根据配套的 SOP 文档 ADGUARDHOME.md,其控制对象是 正在运行的 AdGuardHome 实例暴露的 REST HTTP API(默认 58 个端点、14 个 tag 分组,Basic Auth 保护),而不是某个本地二进制文件——CLI 的职责是"生成结构化命令 → 调用真实 API → 校验响应"。
因此第一个前提是 AdGuardHome 必须在运行。README 给出的两种部署方式:
# Linux - native
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v
# Docker
docker run --name adguardhome -p 3000:3000 adguard/adguardhome
注意 Docker 方式中容器 Web 界面监听 3000 端口,这个端口需要保留在 setup wizard 中——后端客户端 在连接失败抛出的 RuntimeError 信息里也内嵌了同样的部署提示,方便 Agent 在报错时自我修复。
安装
进入 harness 目录以可编辑模式安装:
cd agent-harness
pip install -e .
cli-anything-adguardhome --help
setup.py 声明了入口点 cli-anything-adguardhome=cli_anything.adguardhome.adguardhome_cli:main,依赖为 click>=8.0.0、prompt-toolkit>=3.0.0、requests>=2.28.0,要求 Python >= 3.10,并把 skills/*.md 作为包数据一并打包——这意味着安装后 Agent 可以直接读取 skills/SKILL.md 获得该 harness 的技能描述。
连接配置:环境变量、配置文件与解析优先级
README 支持两种配置方式。一是环境变量:
export AGH_HOST=localhost
export AGH_PORT=3000
export AGH_USERNAME=admin
export AGH_PASSWORD=secret
二是把连接参数写入配置文件(默认路径为 ~/.config/cli-anything-adguardhome.json):
cli-anything-adguardhome --host localhost --port 3000 --username admin --password secret config save
core/project.py 的 load_config 揭示了完整的解析逻辑,四层优先级从高到低:
- CLI flags:
--host、--port、--username、--password(在根命令中host or cfg["host"]覆盖,见 adguardhome_cli.py); - 环境变量:
AGH_HOST、AGH_PORT、AGH_USERNAME、AGH_PASSWORD,只要设置就覆盖文件值; - 配置文件:默认
~/.config/cli-anything-adguardhome.json,可被--config指定其他路径;文件损坏(JSONDecodeError/OSError)时静默回退,不会中断; - 默认值:
localhost:3000,匿名(无 Basic Auth)。
config 子命令组提供三个操作(源码 L164-L206):
config show:显示当前连接设置,密码输出时掩码为***;config save:把当前生效的 host/port/username/password/https 写入配置文件,返回保存路径;config test:真实调用一次server status(即 GET/status)验证连通性,成功时输出{"connected": true, ...}。
基本用法:REPL 与一次性命令
不带子命令直接运行即进入交互式 REPL(这是默认行为,根 group 声明了 invoke_without_command=True,无子命令时 ctx.invoke(repl),见 adguardhome_cli.py)。README 中的典型用法:
# Interactive REPL (default)
cli-anything-adguardhome
# One-shot commands
cli-anything-adguardhome server status
cli-anything-adguardhome filter list
cli-anything-adguardhome --json stats show
--json 是根级 flag,配合 output() 实现双模输出:JSON 模式下 json.dumps(..., indent=2) 全量输出;人类可读模式下 dict 逐行 key: value、list 逐条输出。
README 覆盖的实战命令族:
# Filtering
cli-anything-adguardhome filter add --url https://somehost.com/list.txt --name "My List"
cli-anything-adguardhome filter refresh
# DNS rewrites
cli-anything-adguardhome rewrite add --domain "myserver.local" --answer "192.168.1.50"
cli-anything-adguardhome rewrite list
# Clients
cli-anything-adguardhome clients add --name "My PC" --ip 192.168.1.100
# Stats
cli-anything-adguardhome stats show
cli-anything-adguardhome stats reset
完整命令地图
REPL 内输入 help 会打印与下面一致的命令树(repl 命令 L130-L142),与 SOP 文档中的 CLI Command Map 对应:
cli-anything-adguardhome
├── config show / save / test
├── server status / version / restart
├── filter list / add / remove / enable / disable / refresh / status / toggle
├── blocking parental status/enable/disable
│ safebrowsing status/enable/disable
│ safesearch status/enable/disable
├── blocked-services list / set
├── clients list / add / remove / show
├── stats show / reset / config
├── log show / config / clear
├── rewrite list / add / remove
├── dhcp status / leases / add-static / remove-static
└── tls status
补充几个 README 未逐一展开、但源码中可复制可运行的命令(参数均直接取自对应 click 装饰器):
# 全局过滤开关(on/off)
cli-anything-adguardhome filter toggle on
cli-anything-adguardhome filter remove --url https://somehost.com/list.txt
# 家长控制 / 安全浏览 / 安全搜索
cli-anything-adguardhome blocking parental status
cli-anything-adguardhome blocking safebrowsing enable
cli-anything-adguardhome blocking safesearch disable
# 阻止服务分类(位置参数,可多个)
cli-anything-adguardhome blocked-services list
cli-anything-adguardhome blocked-services set analytics ads
# 查询日志:默认 limit=50、offset=0
cli-anything-adguardhome log show --limit 100 --offset 0
cli-anything-adguardhome log config --enabled --interval 30
cli-anything-adguardhome log clear
# 统计保留天数(days),不带 --interval 则只查询配置
cli-anything-adguardhome stats config --interval 7
# DHCP 静态租约
cli-anything-adguardhome dhcp status
cli-anything-adguardhome dhcp leases
cli-anything-adguardhome dhcp add-static --mac AA:BB:CC:DD:EE:FF --ip 192.168.1.10 --hostname "nas"
# TLS 配置状态
cli-anything-adguardhome tls status
API 客户端层:AdGuardHomeClient
所有命令最终汇聚到 utils/adguardhome_backend.py 中的 AdGuardHomeClient,它把全部 REST 调用封装在 requests.Session 之上。关键实现细节(L11-L27):
- base URL 构造:
{scheme}://{host}:{port}/control;当端口为 80/443 时省略端口段。这与 AdGuardHome API 固定挂载在/control路径下、且 Web 界面端口即 API 端口的事实一致; - HTTPS 自动探测:
https参数为 True 时使用 https;即使未显式指定,端口为 443 时强制升级为 https; - 认证:只要 username 或 password 非空就设置
session.auth(HTTP Basic Auth),请求头统一为Content-Type: application/json; - 响应处理:空响应体返回
{},非 JSON 内容降级为原始文本,避免对空 body 的端点(如/restart)解析失败; - 超时:GET/POST 均为 10 秒超时;
- POST 多形态:dict/list 走 JSON body,str 走
text/plain编码,None 则发送空 POST(adguardhome_backend.pyL55-L69)。
连接失败时 ConnectionError 被统一转成带可操作建议的 RuntimeError(含安装命令与 Docker 命令),这保证了人与 Agent 都能从报错信息中恢复。
命令到 API 端点的调用链
每个命令组都是一个薄的"CLI → core 模块 → API 端点"映射。以 README 中出现的四条命令为例:
| 命令 | core 模块函数 | AdGuardHome API 端点 |
|---|---|---|
server status |
server.get_status | GET /control/status |
filter list |
filtering.get_status | GET /control/filtering/status |
filter add |
filtering.add_filter | POST /control/filtering/add_url(body: name/url/whitelist) |
filter refresh |
filtering.refresh | POST /control/filtering/refresh |
rewrite add |
core/rewrite.py | POST /control/rewrite/add |
stats show / stats reset |
stats.get_stats / reset_stats | GET /control/stats / POST /control/stats_reset |
log show |
log.get_log | GET /control/querylog?limit=&offset= |
有两个值得注意的实现细节:
filter toggle是读-改-写:filtering.set_enabled 先GET /filtering/status读出当前interval(缺省 24 小时),再把{"enabled": ..., "interval": ...}整体 POST 到/filtering/config——避免切换开关时把更新周期重置掉;stats config/log config支持"只读探测"模式:不带写参数时仅 GET 配置(stats_config/querylog_config),带参数时才 POST 更新(log.pyL14-L20 的set_log_config固定附带anonymize_client_ip: False)。
core/ 目录下的模块划分与 API tag 组一一对应:blocking.py(parental/safebrowsing/safesearch/blocked_services)、clients.py、rewrite.py、dhcp.py、stats.py、log.py、server.py(含 GET /tls/status),另有 session.py 与 project.py。从源码结构看,server restart(POST /control/restart,会中断当前连接)与 tls status 是刻意保持只读/低频的操作。
错误处理与 Agent 友好的退出路径
main() 入口 捕获 RuntimeError 与 requests.exceptions.RequestException,行为取决于调用方式:
- 带
--json时输出{"error": "<message>"}并以退出码 1 结束——保证 Agent 的管道永远拿到可解析的 JSON,而不是空输出加 traceback; - 否则把错误消息写入 stderr 后退出。
源码注释明确解释了这一点:后端失败若不被捕获,会表现为链式 traceback,并且 --json 模式将产生不可解析的输出。REPL 模式下同类异常则被 repl 循环 逐行捕获并通过 ReplSkin.error() 显示,不会让会话崩溃——UsageError、RuntimeError、SystemExit 各有分支。
测试策略与验证方式
README 给出的三条测试命令:
cd agent-harness
python3 -m pytest cli_anything/adguardhome/tests/test_core.py -v
python3 -m pytest cli_anything/adguardhome/tests/test_full_e2e.py -v -s
CLI_ANYTHING_FORCE_INSTALLED=1 python3 -m pytest cli_anything/adguardhome/tests/ -v -s
与 tests/TEST.md 和 SOP 文档的 Testing Strategy 一致,分三层:
- 单元测试(tests/test_core.py):用
unittest.mock打桩requests.Session,无需真实 AdGuardHome。例如 TestAdGuardHomeClient 验证了默认base_url == "http://localhost:3000/control"、Basic Auth 设置(session.auth == ("admin", "pass"))、URL 拼接(/status→http://192.168.1.1:8080/control/status)、空响应返回{}等契约; - E2E 测试(tests/test_full_e2e.py):通过 Docker 拉起
adguard/adguardhome镜像,使用 3001 端口做隔离,跑完整命令链路; - 子进程测试:
CLI_ANYTHING_FORCE_INSTALLED=1环境变量触发_resolve_cli("cli-anything-adguardhome")逻辑,直接测试安装后的 CLI 二进制本身。
小结
cli-anything-adguardhome 展示了 CLI-Anything 的典型 harness 形态:一个薄而完整的 Click 应用(adguardhome_cli.py),把 AdGuardHome 的 /control REST API 映射成 14 个命令组;连接配置支持 flag/环境变量/配置文件/默认值四级回退(core/project.py);--json 双模输出与结构化错误路径使其既可被人直接执行,也可被 Agent 在自动化管道中可靠调用。适用前提:AdGuardHome 实例已运行、Basic Auth 凭据可获取、网络可达;所有端点行为以 AdGuardHome 自身 API 为准,harness 仅做封装而不修改服务端。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00