cli-anything-adguardhome 技术指南:基于 REST API 的命令行与 Agent 化 AdGuard Home 管理
AdGuard Home 是一个用 Go 编写的基于 DNS 的全网广告拦截与隐私保护服务,本仓库中的 cli-anything-adguardhome 为它提供了一层"Agent 原生"的命令行封装(harness):CLI 生成结构化命令 → 调用真实 AdGuardHome HTTP API → 校验并解析响应。本文以 ADGUARDHOME.md 为主线,结合 CLI 命令树源码、HTTP 客户端封装 与 测试计划,完整讲解其架构、安装、连接配置、全部命令组、双输出模式与测试策略,读完可直接用它完成过滤规则、DNS 改写、客户端、DHCP、统计与查询日志的日常运维,并让 LLM/Agent 通过标准命令接管整台 AdGuard Home。
一、定位与整体架构
与普通"调用某个二进制"的封装不同,cli-anything-adguardhome 的真实被控对象是运行中的 AdGuardHome HTTP API,CLI 本身不复制业务逻辑,只负责把人类/Agent 的意图翻译成结构化命令,再将 HTTP 响应整理为可读或机器可读输出。ADGUARDHOME.md 将其角色概括为三步闭环:Generate structured commands → Call the real API → Verify responses。
关键连接事实(源自 ADGUARDHOME.md 与 adguardhome_backend.py):
| 项目 | 值 |
|---|---|
| API 基址 | http://<host>:<port>/control/ |
| 认证 | HTTP Basic Auth,请求头形如 Authorization: Basic base64(user:pass) |
| 默认端口 | 3000 |
| OpenAPI 规范 | AdGuardHome 源码中的 openapi/openapi.yaml |
| 上游服务端接口规模 | 58 个端点、14 个标签分组 |
| 本 CLI 覆盖能力 | 服务/过滤/成人内容屏蔽/安全浏览/安全搜索/服务屏蔽/客户端/统计/日志/DNS 改写/DHCP/TLS |
底层 HTTP 客户端 AdGuardHomeClient 的几个工程化细节值得注意,它们直接决定了命令行行为:
- Base URL 构造:默认
http://host:port/control,但做了两个端口特判——port == 443时自动切到https,且当端口为80/443时 URL 中省略端口号(见源码if port == 443: scheme = "https"与port not in (80, 443)分支)。 - 会话级 Basic Auth:只要提供
username或password,就把认证挂到requests.Session上,所有请求统一携带。 - 请求超时:统一
timeout=10秒。 - POST 双编码策略:body 为 dict/list 时以
application/json发送;为字符串时以text/plain编码发送。 - 空响应处理:无内容体时返回
{},避免对空串做 JSON 解析。 - 连接失败引导:
ConnectionError会被包装为RuntimeError,提示信息中直接附带安装命令与 Docker 命令,便于排障。
二、前置条件与安装
CLI 面向的是"已运行"的 AdGuardHome 实例。安装服务端有两种官方方式:
# Linux 原生安装(脚本来自 AdGuardTeam 上游 install.sh,此处仅作为服务端获取途径引用)
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/API 服务监听在 3000 端口,初始化向导阶段需保留该端口,否则 API 基址会不同。
安装本 CLI 包本身(见 README.md 与 setup.py):
cd adguardhome/agent-harness
pip install -e .
cli-anything-adguardhome --help
setup.py 声明了该包的依赖与入口:
- 依赖:
click>=8.0.0(命令框架)、prompt-toolkit>=3.0.0(REPL 交互)、requests>=2.28.0(HTTP); - 入口点:
cli-anything-adguardhome = cli_anything.adguardhome.adguardhome_cli:main,安装后即获得同名可执行命令; - 环境要求:Python
>=3.10; - 包内附带
skills/*.md作为 package_data,供安装后引用技能说明。
三、连接配置:解析优先级与 config 子命令组
ADGUARDHOME.md 明确规定了配置解析顺序,而源码 project.py 的实现严格印证了这一顺序:
- CLI flags:
--host、--port、--username、--password; - 环境变量:
AGH_HOST、AGH_PORT、AGH_USERNAME、AGH_PASSWORD; - 配置文件:
~/.config/cli-anything-adguardhome.json(等价于Path.home() / ".config" / "cli-anything-adguardhome.json"); - 默认值:
localhost:3000。
load_config 的实现顺序是:先以默认值构造 {host: "localhost", port: 3000, username: "", password: "", https: False} → 若配置文件存在则逐 key 覆盖(JSON 解析失败或读文件异常时静默跳过)→ 再让环境变量逐 key 覆盖文件值;随后 adguardhome_cli.py 的根命令 以 host or cfg["host"] 的方式让 CLI flag 最后兜底。因此四层优先级(flags > env > file > defaults)在代码中成立。配置文件样例:
{
"host": "localhost",
"port": 3000,
"username": "admin",
"password": "your-password",
"https": false
}
值得补充的是根命令还额外提供几个文档命令树之外但贯穿全局的开关:
| 全局选项 | 作用 |
|---|---|
--host / --port |
覆盖连接目标 |
--username / --password |
覆盖 Basic Auth 凭据 |
--config <path> |
指定非默认位置的配置文件 |
--https |
强制使用 HTTPS(--port 443 时后端已自动切换) |
--json |
切换为机器可读 JSON 输出 |
config 子命令
cli-anything-adguardhome config show # 展示当前生效的连接配置(密码打码为 ***)
cli-anything-adguardhome config save # 将当前连接参数持久化写入配置文件
cli-anything-adguardhome config test # 请求 GET /control/status 验证连通性
config save 会通过 save_config 在 ~/.config/ 下自动创建目录并写入 JSON;config test 则直接调用 server 状态接口并返回 connected: true 及状态详情。Agent 或脚本在批量操作前,建议先执行 config test 确认目标实例可达(这一点在下方 SKILL.md 的"For AI Agents"一节被明确列为最佳实践)。
四、CLI 命令树总览
ADGUARDHOME.md 给出了完整的命令树,现照录并展开(源码层面每个叶子命令都可在 adguardhome_cli.py 中找到对应装饰器与 click group):
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
调用某个子命令时,同一命令树中的命令都以 cli-anything-adguardhome <group> <verb> [options] 形式给出;help 别名同时支持 -h。
五、API 标签分组与命令组的映射
AdGuardHome 上游把 REST API 划分为若干标签组。下表来自 ADGUARDHOME.md,左侧为 API 标签组、右侧为本 CLI 实际落地的关键命令:
| Group(API) | Description | Key Endpoints | 对应 CLI 能力 |
|---|---|---|---|
global |
服务器设置与总控 | /status、/version、/restart |
server status/version/restart |
filtering |
基于规则列表的过滤 | /filtering/status、/filtering/add_url、/filtering/remove_url |
filter list/add/remove/enable/disable/refresh/status/toggle |
blocked_services |
屏蔽服务品类 | /blocked_services/get、/blocked_services/set |
blocked-services list/set |
clients |
已知客户端设备 | /clients、/clients/add、/clients/delete |
clients list/add/remove/show |
stats |
DNS 查询统计 | /stats、/stats_reset、/stats_config |
stats show/reset/config |
log |
查询日志 | /querylog、/querylog_config、/querylog_clear |
log show/config/clear |
dhcp |
内置 DHCP 服务 | /dhcp/status、/dhcp/leases、/dhcp/set_config |
dhcp status/leases/add-static/remove-static |
rewrite |
DNS 改写规则 | /rewrite/list、/rewrite/add、/rewrite/delete |
rewrite list/add/remove |
parental |
成人内容屏蔽 | /parental/status、/parental/enable、/parental/disable |
blocking parental status/enable/disable |
safebrowsing |
恶意软件/钓鱼拦截 | /safebrowsing/status、/safebrowsing/enable、/safebrowsing/disable |
blocking safebrowsing status/enable/disable |
safesearch |
强制安全搜索 | /safesearch/status、/safesearch/enable、/safesearch/disable |
blocking safesearch status/enable/disable |
tls |
HTTPS/DoH/DoT 设置 | /tls/status、/tls/configure、/tls/validate |
tls status |
下文按功能域逐一给出可复制、可运行的命令示例,并标注其底层调用的 HTTP 端点与源码模块路径,方便读者对照 core 目录 理解实现。
六、server 与 filter:服务器总控和过滤规则生命周期
server(核心实现见 core/server.py)
cli-anything-adguardhome server status # GET /status —— 展示保护状态(adult/enabled 等)
cli-anything-adguardhome server version # GET /version —— 展示 AdGuardHome 版本
cli-anything-adguardhome server restart # POST /restart —— 重启服务,无响应体时输出 {"restarted": true}
filter(核心实现见 core/filtering.py)
过滤列表管理是整套工具使用最密集的部分:
# 查看全部过滤订阅与整体状态
cli-anything-adguardhome filter list # GET /filtering/status
# 只取开关状态与订阅数量
cli-anything-adguardhome filter status # 结果含 enabled 与 filters_count
# 全局开/关过滤(参数为 on 或 off)
cli-anything-adguardhome filter toggle on
cli-anything-adguardhome filter toggle off # POST /filtering/config {"enabled": bool, "interval": ...}
# 新增一个订阅(URL 为规则列表地址,name 为该订阅命名)
cli-anything-adguardhome filter add --url https://example.com/adblock-list.txt --name "My List"
cli-anything-adguardhome filter add --url https://example.com/whitelist.txt --name "My Whitelist" --whitelist
# 底层 POST /filtering/add_url,body = {"name","url","whitelist"}
# 按 URL 移除订阅(--whitelist 用于指明操作对象是白名单)
cli-anything-adguardhome filter remove --url https://example.com/adblock-list.txt
# 单独启用/禁用某一订阅
cli-anything-adguardhome filter enable --url https://example.com/list.txt --name "My List"
cli-anything-adguardhome filter disable --url https://example.com/list.txt --name "My List"
# 底层 POST /filtering/set_url,body 的 data.enabled 决定启用与否
# 手动触发全量列表刷新(拉取规则最新版本)
cli-anything-adguardhome filter refresh # POST /filtering/refresh {"whitelist": bool}
从 filtering.py 可看到两个实现要点:set_enabled 在开关过滤时会先 GET /filtering/status 读出当前 interval(默认 24 小时),再原样回填到 POST /filtering/config,避免切换开关时把定时刷新周期改掉;set_filter_url 的启用/禁用通过同一端点携带 data.enabled 实现,即上游 /filtering/set_url 的语义。
七、blocking / blocked-services:内容安全与品类屏蔽
blocking 命令组统管三类内容安全功能,接口语义完全对称(status 走 GET、enable/disable 走 POST,实现见 core/blocking.py):
# 成人内容 / 家长控制
cli-anything-adguardhome blocking parental status
cli-anything-adguardhome blocking parental enable
cli-anything-adguardhome blocking parental disable
# 恶意软件与钓鱼网站拦截
cli-anything-adguardhome blocking safebrowsing status
cli-anything-adguardhome blocking safebrowsing enable
cli-anything-adguardhome blocking safebrowsing disable
# 强制安全搜索(搜索引擎自动套用安全模式)
cli-anything-adguardhome blocking safesearch status
cli-anything-adguardhome blocking safesearch enable
cli-anything-adguardhome blocking safesearch disable
对应的底层端点分别为 /parental/{status,enable,disable}、/safebrowsing/{status,enable,disable}、/safesearch/{status,enable,disable}。
blocked-services 用于按服务品类整体屏蔽(例如社交网络、视频流媒体、即时通讯等,服务 id 为 AdGuardHome 内置 schema 定义):
# 查看当前被屏蔽的服务
cli-anything-adguardhome blocked-services list # GET /blocked_services/get
# 覆盖式设定屏蔽集合(可传多个 id)
cli-anything-adguardhome blocked-services set whatsapp telegram # POST /blocked_services/set {"ids": [...]}
八、clients / rewrite / dhcp:设备、DNS 改写与 DHCP 租约
clients(核心实现见 core/clients.py)
# 列出所有已配置客户端
cli-anything-adguardhome clients list # GET /clients
# 新增客户端(以 name 命名、ip 作为身份标识之一)
cli-anything-adguardhome clients add --name "My PC" --ip 192.168.1.100 # POST /clients/add
# 按名字移除
cli-anything-adguardhome clients remove --name "My PC" # POST /clients/delete
# 查看单个客户端详情(本地在 clients 列表中按 name 匹配后输出)
cli-anything-adguardhome clients show --name "My PC"
从 adguardhome_cli.py 的 clients_add 可见:--ip 会被放入 ids=[ip] 数组提交,与 AdGuardHome 客户端模型"一个客户端可有多个标识(IP/MAC/ClientID)"对应。
rewrite(核心实现见 core/rewrite.py)
DNS 改写常用于搭建本地域名指向内网 IP,或把某域名固定解析到指定地址:
# 新增规则:域名 -> 解析答案(可为 IP 或 CNAME)
cli-anything-adguardhome rewrite add --domain "myserver.local" --answer "192.168.1.50" # POST /rewrite/add
# 查看全部改写规则
cli-anything-adguardhome rewrite list # GET /rewrite/list
# 删除某条规则(需与新增时一致地同时给出 domain 与 answer)
cli-anything-adguardhome rewrite remove --domain "myserver.local" --answer "192.168.1.50" # POST /rewrite/delete
dhcp(核心实现见 core/dhcp.py)
# DHCP 服务状态
cli-anything-adguardhome dhcp status # GET /dhcp/status
# 当前活动租约
cli-anything-adguardhome dhcp leases # GET /dhcp/leases
# 添加静态租约(mac + ip 必填,hostname 可空)
cli-anything-adguardhome dhcp add-static --mac AA:BB:CC:DD:EE:FF --ip 192.168.1.50 --hostname nas
# 移除静态租约
cli-anything-adguardhome dhcp remove-static --mac AA:BB:CC:DD:EE:FF --ip 192.168.1.50
九、stats / log:观测 DNS 查询统计与查询日志
stats(核心实现见 core/stats.py)
# 查看统计(总查询数、被拦截数、按客户端/域名分类等)
cli-anything-adguardhome stats show # GET /stats
# 清空统计
cli-anything-adguardhome stats reset # POST /stats_reset
# 不带参数查看当前保留周期配置;带 --interval 则更新保留天数
cli-anything-adguardhome stats config
cli-anything-adguardhome stats config --interval 30 # 查询/更新 POST /stats_config {"interval": N}
log(核心实现见 core/log.py)
# 查看最近查询记录(默认 limit=50,可用 offset 翻页)
cli-anything-adguardhome log show # GET /querylog?limit=50&offset=0
cli-anything-adguardhome log show --limit 200 --offset 100
# 查看当前日志配置;或同时更新开关与保留天数
cli-anything-adguardhome log config
cli-anything-adguardhome log config --enabled --interval 90 # 启用并保留 90 天
cli-anything-adguardhome log config --disabled # 关闭查询日志
# 清空查询日志
cli-anything-adguardhome log clear # POST /querylog_clear
adguardhome_cli.py 的 log_config 实现了一个细节:当只给 --enabled/--disabled 或只给 --interval 时,会先 GET 当前配置读出另一项(interval 缺省沿用现有值、默认 90 天),再整体 POST,避免部分更新覆盖已有设置。
tls
# 查看 HTTPS/DoH/DoT 配置状态
cli-anything-adguardhome tls status # GET /tls/status(复用 core/server.py 的 get_tls_status)
十、REPL 交互模式与双输出格式
不带任何子命令直接运行会进入 REPL(实现见 adguardhome_cli.py 的 repl 命令,基于 prompt-toolkit 皮肤 repl_skin.py):
cli-anything-adguardhome
# 横幅显示版本与连接目标,交互提示符包含 host:port
# 支持 help(列出全部命令)、exit / quit 退出;输入行经 shlex 切分后复用于同一命令树
REPL 内可直接输入 server status、filter list 等与命令行完全一致的短语。
输出层面所有命令都支持两种格式(来自 SKILL.md 的 "Output Formats"):
- 人类可读(默认):dict 按
key: value逐行打印,list 逐元素输出; - 机器可读(
--json):整个结果以indent=2的 JSON 输出,供 Agent/LLM 解析。
cli-anything-adguardhome filter list
cli-anything-adguardhome --json filter list
此外根命令还隐藏了 错误输出契约:main() 捕获 RuntimeError 与 requests 异常;当检测到 --json 时,向 stdout 输出 {"error": "..."} 结构化错误并 exit(1),非 JSON 模式下则向 stderr 打印可读信息——这保证了 Agent 用 --json 驱动的管道永远不会拿到不可解析的输出。
十一、面向 AI Agent 的使用规约
包内技能文档 SKILL.md(发布分发副本位于 skills/cli-anything-adguardhome/SKILL.md)在其 frontmatter 中明确描述自身定位:"Designed for AI agents and power users who need to manage filtering, DNS rewrites, clients, DHCP, and query logs without a GUI",并为程序化调用约定了一组规约:
- 始终加
--json:保证输出可解析,不依赖终端格式化; - 检查返回码:0 表示成功,非 0 表示失败;
- 失败时读 stderr:错误信息统一走标准错误流;
- 文件操作使用绝对路径;
- 先
config test再执行其它命令:把连通性校验作为一切批量操作的前置步骤。
十二、测试策略:三层验证
TEST.md 与源码测试把测试分成三层,逐层加固"命令 → 真实 API → 响应校验"的闭环:
1. 单元测试(tests/test_core.py)——无需真实 AdGuardHome:
- 用
unittest.mock桩掉 HTTP 调用,逐端点断言 method、URL、body 是否拼对; - 覆盖 HTTP 客户端构造(默认值、带认证、URL 拼接)、空响应、连接错误包装、配置加载(默认值/文件覆盖/环境变量优先/保存)以及各 core 模块的端点映射;
- 例如
test_client_url_construction、test_load_config_env_override、test_add_filter(断言POST /filtering/add_url的请求体)。
2. E2E 测试(tests/test_full_e2e.py)——隔离的真实实例:
- 通过 fixture 拉起官方
adguard/adguardhomeDocker 容器,映射到 3001 端口避免与本地实例冲突(测试端口常量为AGH_TEST_PORT = 3001); - 启动后轮询
GET /control/status等待就绪,再调用/control/install/configure完成初始化向导(设置 web/dns 监听与 admin 账号密码); - 跑完整业务流:服务器状态、过滤列表、rewrite 生命周期(增→查→删)、stats、config test。文件末尾记录的实测结果为 36/36 全部通过:单元 24、子进程 7、Docker E2E 5(对 AdGuardHome v0.107.73)。
3. 子进程测试(tests/test_full_e2e.py 的 TestCLISubprocess)——验证已安装 CLI 本身:
_resolve_cli("cli-anything-adguardhome")优先解析 PATH 中的已安装命令;未安装时回退python -m cli_anything.adguardhome.adguardhome_cli(开发态);- 设置
CLI_ANYTHING_FORCE_INSTALLED=1可强制要求真实已安装二进制,用于发布前校验 console script 是否可用; - 用例包括
--help退出码为 0、--json config show输出合法 JSON、各子命令组的 help 是否完整列出。
本地复现全部测试的命令:
cd adguardhome/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 # 需要 docker pull adguard/adguardhome
CLI_ANYTHING_FORCE_INSTALLED=1 python3 -m pytest cli_anything/adguardhome/tests/ -v -s
十三、从文档到源码的实现索引
若想深入理解某一命令背后的实现,可按下面索引直达对应源码(所有路径均相对本仓库根目录):
| 关注点 | 文件 |
|---|---|
| SOP 总览与 API 分组 | adguardhome/agent-harness/ADGUARDHOME.md |
| CLI 入口、命令树、全局选项、REPL、错误契约 | adguardhome/agent-harness/cli_anything/adguardhome/adguardhome_cli.py |
| HTTP 客户端与 URL/认证/超时细节 | adguardhome/agent-harness/cli_anything/adguardhome/utils/adguardhome_backend.py |
| 配置解析优先级与持久化 | adguardhome/agent-harness/cli_anything/adguardhome/core/project.py |
| server / filter / blocking / clients / rewrite / dhcp / stats / log 各 core 模块 | adguardhome/agent-harness/cli_anything/adguardhome/core/ |
| 包内技能文档与 Agent 规约 | adguardhome/agent-harness/cli_anything/adguardhome/skills/SKILL.md |
| 分发到 skills 目录的技能副本 | skills/cli-anything-adguardhome/SKILL.md |
| 打包元数据与 console script 入口 | adguardhome/agent-harness/setup.py |
| 三层测试计划与实测结果 | adguardhome/agent-harness/cli_anything/adguardhome/tests/TEST.md |
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 StartedRust0627
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