From 3be0915a7133f4ac5a65afaf67fe3cab3816dccd Mon Sep 17 00:00:00 2001 From: Arvin <62139570+wnlen@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:25:15 +0800 Subject: [PATCH] Make clash the primary CLI command --- README.md | 174 ++++--- install.sh | 2 +- resources/bin/README.md | 2 +- scripts/core/alias.sh | 70 +-- scripts/core/clashctl.sh | 634 ++++++++++++----------- scripts/core/common.sh | 93 +++- scripts/core/completion.sh | 7 +- scripts/core/config.sh | 40 +- scripts/core/update.sh | 2 +- scripts/dev/check-clash-command-entry.sh | 128 +++++ scripts/dev/check-select-direct.sh | 2 +- scripts/init/script.sh | 13 +- 12 files changed, 687 insertions(+), 480 deletions(-) create mode 100644 scripts/dev/check-clash-command-entry.sh diff --git a/README.md b/README.md index 41921b8..f94fdb1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ - 🚀 **自动识别系统架构**:自动下载并使用对应 Clash 内核 - 🧪 **端口自动检测与分配**:避免冲突 -- 🔄 **多订阅管理**:可以保存多个订阅,通过 `clashctl use` 切换当前主订阅。 +- 🔄 **多订阅管理**:可以保存多个订阅,通过 `clash use` 切换当前主订阅。 - 💫 **节点选择**:使用编号交互选择策略组和节点。 - 🌐 **Tun 模式**:用于透明代理接管场景(需root方式安装)。 - 🧠 **Mixin 机制**:可按需追加/覆盖 Clash 配置 @@ -56,55 +56,57 @@ bash install.sh ```bash 〽️ 常用命令 - clashon 🚀 开启代理 - clashoff ⛔ 关闭代理 - clashctl select 💫 选择节点 - clashctl mode 🧭 查看或切换路由模式 - clashtest 🌐 测试当前节点访问 Google / YouTube + clashon 🚀 开启代理 + clashoff ⛔ 关闭代理 + clash select 💫 选择节点 + clash mode 🧭 查看或切换路由模式 + clashtest 🌐 测试当前节点访问 Google / YouTube 🕹️ 控制台 - clashui 🕹️ 查看 Web 控制台 + clashui 🕹️ 查看 Web 控制台 📦 订阅 - clashctl add ➕ 添加订阅 - clashctl add local ➕ 从 runtime/subscriptions 导入本地订阅 - clashctl use 💱 切换订阅 - clashctl ls 📜 查看订阅列表 + clash add ➕ 添加订阅 + clash add local ➕ 从 runtime/subscriptions 导入本地订阅 + clash use 💱 切换订阅 + clash ls 📜 查看订阅列表 🔐 密钥管理 - clashctl secret 🔑 查看密钥 - clashctl secret 123 🔐 设置密钥 + clash secret 🔑 查看密钥 + clash secret 123 🔐 设置密钥 📌 高级 - clashctl lan 🏠 局域网代理管理 - clashctl tun 🧪 Tun 模式管理(需root方式安装) - clashctl boot 🚦 开机代理接管管理 - clashctl mixin 🧩 Mixin 配置管理 - clashctl relay 🔗 多跳节点管理 - clashctl sub 🧩 订阅高级管理(启用 / 禁用 / 重命名 / 删除) - clashctl upgrade 🚀 升级当前或指定内核 - clashctl update 🔄 更新项目代码 + clash lan 🏠 局域网代理管理 + clash tun 🧪 Tun 模式管理(需root方式安装) + clash boot 🚦 开机代理接管管理 + clash mixin 🧩 Mixin 配置管理 + clash relay 🔗 多跳节点管理 + clash sub 🧩 订阅高级管理(启用 / 禁用 / 重命名 / 删除) + clash upgrade 🚀 升级当前或指定内核 + clash update 🔄 更新项目代码 📜 日志 - clashctl doctor 🩺 诊断面板 - clashctl log/logs 📜 查看日志 + clash doctor 🩺 诊断面板 + clash log/logs 📜 查看日志 -💡 显示更多帮助命令:clashctl -h +💡 显示更多帮助命令:clash -h ``` +`clashctl` 旧入口继续保留兼容;新文档、帮助和终端提示统一使用更简洁的 `clash`。 + ------ ## 🧭 路由模式与节点连通性 ```bash -clashctl mode -clashctl mode global -clashctl mode rule -clashctl mode direct +clash mode +clash mode global +clash mode rule +clash mode direct -clashctl test +clash test clashtest clashtest "节点选择" ``` -- `clashctl mode` 查看当前的 Clash 路由模式,支持切换 `rule`、`global`、`direct`。 -- `global` 模式使用 `GLOBAL` 策略组当前选择的策略组或节点;如需调整可执行 `clashctl select`。 -- `clashctl test` 与 `clashtest` 等价,默认测试当前路由模式对应的策略组当前选择能否访问 Google 和 YouTube,并显示延迟。 +- `clash mode` 查看当前的 Clash 路由模式,支持切换 `rule`、`global`、`direct`。 +- `global` 模式使用 `GLOBAL` 策略组当前选择的策略组或节点;如需调整可执行 `clash select`。 +- `clash test` 与 `clashtest` 等价,默认测试当前路由模式对应的策略组当前选择能否访问 Google 和 YouTube,并显示延迟。 - 连通性测试不会切换节点;任一目标不可访问时命令返回非零状态,便于脚本判断。 ------ @@ -134,7 +136,7 @@ $ clashsecret ``` - 可通过浏览器打开 `Web` 控制台进行可视化操作,例如切换节点、查看日志等。 -- `clashctl secret` 与 `clashsecret` 都支持无参数查看、有参数直接设置。 +- `clash secret` 与 `clashsecret` 都支持无参数查看、有参数直接设置。 - 默认使用 [zashboard](https://github.com/Zephyruso/zashboard) 作为控制台前端,如需更换可自行配置。 - 控制台中的“浏览器出口 IP”由访问面板的浏览器发起查询,可能与远端 Mihomo 的代理出口不同;远端出口请在服务器上通过本地代理端口查询。 - 运行配置会把 `external-ui` 指向本地 Dashboard 目录,并把 `external-ui-url` 指向 zashboard 的 `dist.zip` 下载地址;面板内 `/upgrade/ui` 会使用该地址更新前端。 @@ -147,32 +149,32 @@ $ clashsecret 项目默认开启局域网代理:运行配置会写入 `allow-lan: true`,并把 `external-controller` 绑定到 `0.0.0.0`,避免订阅文件里的 `allow-lan: false` 覆盖项目默认值。 ```bash -clashctl lan status -clashctl lan on -clashctl lan off +clash lan status +clash lan on +clash lan off ``` 开启后,同一局域网设备可把 HTTP / SOCKS 代理地址设置为 `http://<本机局域网IP>:`,端口默认是 `7890`;如访问不了,请检查系统防火墙是否放行该端口。 ------ -OpenWrt 下 root/system 安装会把 `clashctl`、`clashon`、`clashoff` 等命令入口写入 `/usr/bin`,运行状态、日志和内核二进制仍保存在项目目录的 `runtime/` 下。仅脚本模式不会注册开机自启,设备重启后需要重新执行 `clashon`。 +OpenWrt 下 root/system 安装会把 `clash`、`clashon`、`clashoff` 等命令入口写入 `/usr/bin`,运行状态、日志和内核二进制仍保存在项目目录的 `runtime/` 下。仅脚本模式不会注册开机自启,设备重启后需要重新执行 `clashon`。 ## 🧰 常用管理命令 ### 多订阅管理 ``` -clashctl add <订阅链接> <名称> -clashctl use -clashctl ls -clashctl sub update +clash add <订阅链接> <名称> +clash use +clash ls +clash sub update clashsub update # 兼容旧版命令 -clashctl sub -clashctl sub list -clashctl sub enable <名称> -clashctl sub disable <名称> -clashctl sub rename <旧名称> <新名称> -clashctl sub remove <名称> +clash sub +clash sub list +clash sub enable <名称> +clash sub disable <名称> +clash sub rename <旧名称> <新名称> +clash sub remove <名称> ``` WSL / 普通用户如果无权写入 `/etc/environment`,`clashon` 会自动降级:运行时照常启动,当前 Shell 代理变量生效;系统代理持久接管和开机代理保持不可用。 @@ -182,20 +184,20 @@ WSL / 普通用户如果无权写入 `/etc/environment`,`clashon` 会自动降 推荐使用交互导入,放置目录为:`$PROJECT_DIR/runtime/subscriptions/` ```bash -clashctl add local +clash add local # 输入:clash.yaml ``` 实际等价于: ```bash -clashctl add "file://$PROJECT_DIR/runtime/subscriptions/clash.yaml" +clash add "file://$PROJECT_DIR/runtime/subscriptions/clash.yaml" ``` 进阶用法:也可以直接使用 `file://` 绝对路径导入: ```bash -clashctl add "file:///绝对路径/clash.yaml" home +clash add "file:///绝对路径/clash.yaml" home ``` 支持格式: @@ -207,11 +209,11 @@ clashctl add "file:///绝对路径/clash.yaml" home ### 开机接管(内核 + 代理) ```bash -clashctl boot status -clashctl boot on -clashctl boot off -clashctl boot runtime on|off|status -clashctl boot proxy on|off|status +clash boot status +clash boot on +clash boot off +clash boot runtime on|off|status +clash boot proxy on|off|status ``` - `boot runtime`:只管理内核开机自启(systemd / systemd-user 可用,script 后端为 `unsupported`)。 @@ -244,11 +246,11 @@ opkg install bash curl tar gzip coreutils-readlink unzip ```bash bash install.sh clashon -clashctl status +clash status clashoff ``` -OpenWrt 下 root/system 安装会把 `clashctl`、`clashon`、`clashoff` 等命令入口写入 `/usr/bin`,运行状态、日志和内核二进制仍保存在项目目录的 `runtime/` 下。仅脚本模式不会注册开机自启,设备重启后需要重新执行 `clashon`。 +OpenWrt 下 root/system 安装会把 `clash`、`clashon`、`clashoff` 等命令入口写入 `/usr/bin`,运行状态、日志和内核二进制仍保存在项目目录的 `runtime/` 下。仅脚本模式不会注册开机自启,设备重启后需要重新执行 `clashon`。 ## 🏗️ 架构设计架构简述 @@ -258,7 +260,7 @@ OpenWrt 下 root/system 安装会把 `clashctl`、`clashon`、`clashoff` 等命 用户入口层。 -- `clashctl` +- `clash` - `clashon` - `clashoff` - `status` @@ -341,13 +343,13 @@ CLASH_GH_PROXY=https://ghfast.top ``` 可用镜像列表参考: -当前镜像使用状态可通过 `clashctl doctor` 查看。 +当前镜像使用状态可通过 `clash doctor` 查看。 ### 内置运行依赖 当前正式支持的架构为 `amd64`、`arm64`、`armv7`。超出这三种架构时会明确失败,不会伪装成已支持。 -如果安装环境访问 GitHub 很慢,可以把 Mihomo、yq、subconverter 的刚需文件跟随项目一起分发。安装和 `clashctl upgrade` 会优先读取 `resources/bin` 中与当前版本、架构对应的精确文件名;本地没有对应文件时,会回退到原来的远程下载逻辑,不影响后续升级内核。 +如果安装环境访问 GitHub 很慢,可以把 Mihomo、yq、subconverter 的刚需文件跟随项目一起分发。安装和 `clash upgrade` 会优先读取 `resources/bin` 中与当前版本、架构对应的精确文件名;本地没有对应文件时,会回退到原来的远程下载逻辑,不影响后续升级内核。 Clash 仅作为兼容内核处理,固定走远程下载,不会命中 `resources/bin` 中的本地资源。 @@ -381,19 +383,19 @@ resources/geo/Country.mmdb 查看当前模板: ```bash -clashctl mixin +clash mixin ``` 编辑: ```bash -clashctl mixin edit +clash mixin edit ``` 查看最终运行配置: ```bash -clashctl mixin runtime +clash mixin runtime ``` ### @@ -403,10 +405,10 @@ clashctl mixin runtime ## 🔄 更新 ```bash -clashctl update -clashctl upgrade -clashctl upgrade mihomo -clashctl upgrade clash +clash update +clash upgrade +clash upgrade mihomo +clash upgrade clash ``` `update` 用于更新项目代码与运行依赖。`upgrade` 用于升级当前或指定代理内核。 @@ -416,10 +418,10 @@ clashctl upgrade clash ## 🧩 Mixin 配置 ```bash -clashctl mixin -clashctl mixin edit -clashctl mixin raw -clashctl mixin runtime +clash mixin +clash mixin edit +clash mixin raw +clash mixin runtime ``` Mixin 是运行配置补丁,不是订阅管理。它优先通过 `runtime/mixin.yaml`(兼容读取 `config/mixin.yaml`)对当前 active 订阅生成的运行配置执行: @@ -451,7 +453,7 @@ append: 编辑后执行: ```bash -clashctl mixin edit +clash mixin edit ``` 它会重新生成配置;如果代理正在运行,会自动重启应用。 @@ -468,8 +470,8 @@ clashui 按域名小范围测试: ```bash -clashctl relay add 多跳-示例 节点A 节点B --domain example.com -clashctl relay list +clash relay add 多跳-示例 节点A 节点B --domain example.com +clash relay list ``` 也可以使用快捷入口:`clashrelay list`。 @@ -477,13 +479,13 @@ clashctl relay list 全局接管: ```bash -clashctl relay add 全局多跳 节点A 节点B --match +clash relay add 全局多跳 节点A 节点B --match ``` `--match` 会让所有未提前命中的流量走多跳,建议先用 `--domain` 验证链路。删除多跳配置: ```bash -clashctl relay remove 多跳-示例 +clash relay remove 多跳-示例 ``` ------ @@ -491,22 +493,22 @@ clashctl relay remove 多跳-示例 ## 🌐 Tun 模式 ```bash -clashctl tun on -clashctl tun off -clashctl tun on-proxy-off -clashctl tun off-proxy-on -clashctl tun doctor -clashctl tun logs +clash tun on +clash tun off +clash tun on-proxy-off +clash tun off-proxy-on +clash tun doctor +clash tun logs ``` -Tun 用于透明接管链路。`tun on` 只负责开启 Tun,不会自动关闭系统代理;如需切换到 Tun 接管并关闭系统代理,使用 `clashctl tun on-proxy-off`。如需关闭 Tun 并恢复普通系统代理模式,使用 `clashctl tun off-proxy-on`。 +Tun 用于透明接管链路。`tun on` 只负责开启 Tun,不会自动关闭系统代理;如需切换到 Tun 接管并关闭系统代理,使用 `clash tun on-proxy-off`。如需关闭 Tun 并恢复普通系统代理模式,使用 `clash tun off-proxy-on`。 -`clashctl doctor` 会检查 Tun 与系统代理是否同时开启;如果同时开启,会提示 `clashctl tun on-proxy-off`,避免流量接管路径重复或排障混淆。 +`clash doctor` 会检查 Tun 与系统代理是否同时开启;如果同时开启,会提示 `clash tun on-proxy-off`,避免流量接管路径重复或排障混淆。 `tun on` 是动作反馈,展示当前关键配置和简短状态;完整证据请看: ```bash -clashctl tun doctor +clash tun doctor ``` Tun 判断不会简单把 `root` 等同于拥有 `CAP_NET_ADMIN`,也不会把 main table 默认路由未切换直接等同于 Tun 未生效。诊断会结合运行后端、容器环境、进程能力、Tun adapter、policy routing、路由表和日志证据。 @@ -519,7 +521,7 @@ Tun 判断不会简单把 `root` 等同于拥有 `CAP_NET_ADMIN`,也不会把 bash uninstall.sh ``` -默认执行完整卸载:停止 mihomo/subconverter,关闭系统代理持久接管,删除 systemd/脚本入口、`clashctl`、命令补全、shell alias、shell proxy 持久状态、controller secret 和运行目录。完整卸载完成后,脚本会提示其他卸载方式。 +默认执行完整卸载:停止 mihomo/subconverter,关闭系统代理持久接管,删除 systemd/脚本入口、`clash`、命令补全、shell alias、shell proxy 持久状态、controller secret 和运行目录。完整卸载完成后,脚本会提示其他卸载方式。 `--purge-runtime` 保留为兼容别名;默认卸载已经会清理运行目录。 diff --git a/install.sh b/install.sh index 2215ec5..7eabe99 100755 --- a/install.sh +++ b/install.sh @@ -65,7 +65,7 @@ if [ -n "$(subscription_url 2>/dev/null || true)" ]; then if [ -f "$RUNTIME_DIR/tmp/subscription-invalid-preview.txt" ]; then echo "🧾 调试预览:$RUNTIME_DIR/tmp/subscription-invalid-preview.txt" fi - echo "👉 下一步:clashctl doctor" + echo "👉 下一步:clash doctor" echo exit 1 fi diff --git a/resources/bin/README.md b/resources/bin/README.md index 1b05ea3..6199630 100644 --- a/resources/bin/README.md +++ b/resources/bin/README.md @@ -1,6 +1,6 @@ # 内置运行依赖 -如果安装时 GitHub 下载太慢,可以把 Mihomo、yq、subconverter 提前放到本目录。安装和 `clashctl upgrade` 会优先读取这里的文件;没有匹配文件时,仍会回退到原来的下载逻辑。 +如果安装时 GitHub 下载太慢,可以把 Mihomo、yq、subconverter 提前放到本目录。安装和 `clash upgrade` 会优先读取这里的文件;没有匹配文件时,仍会回退到原来的下载逻辑。 Clash 是兼容内核,固定走远程下载,不使用本目录中的本地资源。 diff --git a/scripts/core/alias.sh b/scripts/core/alias.sh index 0022b1a..513356f 100644 --- a/scripts/core/alias.sh +++ b/scripts/core/alias.sh @@ -216,12 +216,12 @@ _clash_alias_unset_shell_proxy() { } _clash_alias_status_next() { - _clashctl_real status-next 2>/dev/null || echo "clashctl status" + _clashctl_real status-next 2>/dev/null || echo "clash status" } _clash_alias_prepare_on() { # 这里不直接自己做 regenerate / restart / fallback, - # 而是统一交给 clashctl on 主链处理,避免双执行链。 + # 而是统一交给 clash on 主链处理,避免双执行链。 # shell 层只负责“闭环体验”,不抢 runtime / build 的职责。 return 0 } @@ -258,12 +258,12 @@ _clash_alias_run_on() { if [ "$on_rc" -ne 0 ]; then if _clash_alias_export_system_proxy; then - echo "🚨 clashctl on 返回非 0,但系统代理已写入,继续同步当前 Shell(底层返回码:$on_rc)" >&2 + echo "🚨 clash on 返回非 0,但系统代理已写入,继续同步当前 Shell(底层返回码:$on_rc)" >&2 if [ -s "$on_output" ]; then sed 's/^/ /' "$on_output" >&2 fi elif _clash_alias_proxy_on_system; then - echo "🚨 clashctl on 返回非 0,已通过 proxy on 继续同步当前 Shell(底层返回码:$on_rc)" >&2 + echo "🚨 clash on 返回非 0,已通过 proxy on 继续同步当前 Shell(底层返回码:$on_rc)" >&2 if [ -s "$on_output" ]; then sed 's/^/ /' "$on_output" >&2 fi @@ -304,7 +304,7 @@ _clash_alias_auto_restore_proxy() { return 0 } -clashctl() { +_clash_command() { case "${1:-}" in on) shift || true @@ -349,101 +349,111 @@ clashctl() { esac } -# 快捷入口全部收敛到 clashctl 函数 +clashctl() { + _clash_command "$@" +} + +if [ "${CLASH_FOR_LINUX_CLASH_ENTRY_MANAGED:-false}" = "true" ]; then + clash() { + _clash_command "$@" + } +fi + +# 快捷入口全部收敛到统一命令函数 clashon() { - clashctl on "$@" || return $? + _clash_command on "$@" || return $? } clashoff() { - clashctl off "$@" || return $? + _clash_command off "$@" || return $? } clashproxy() { case "${1:-show}" in on) - clashctl proxy on + _clash_command proxy on ;; off) - clashctl proxy off + _clash_command proxy off ;; show|status) - clashctl proxy show + _clash_command proxy show ;; groups) - clashctl proxy groups + _clash_command proxy groups ;; current) shift || true - clashctl proxy current "$@" + _clash_command proxy current "$@" ;; nodes) shift || true - clashctl proxy nodes "$@" + _clash_command proxy nodes "$@" ;; select) shift || true - clashctl proxy select "$@" + _clash_command proxy select "$@" ;; *) echo "📜 用法:clashproxy [show|on|off|groups|current|nodes|select]" - echo "💡 主路径切节点请使用:clashselect 或 clashctl select" + echo "💡 主路径切节点请使用:clashselect 或 clash select" return 2 ;; esac } clashls() { - clashctl ls "$@" + _clash_command ls "$@" } clashsub() { - clashctl sub "$@" + _clash_command sub "$@" } clashselect() { - clashctl select "$@" + _clash_command select "$@" } clashtest() { - clashctl test "$@" + _clash_command test "$@" } clashui() { - clashctl ui "$@" + _clash_command ui "$@" } clashsecret() { - clashctl secret "$@" + _clash_command secret "$@" } clashtun() { - clashctl tun "$@" + _clash_command tun "$@" } clashrelay() { - clashctl relay "$@" + _clash_command relay "$@" } clashupgrade() { - clashctl upgrade "$@" + _clash_command upgrade "$@" } clashmixin() { case "${1:-}" in -e|--edit) - clashctl mixin edit + _clash_command mixin edit ;; -c|--raw) - clashctl mixin raw + _clash_command mixin raw ;; -r|--runtime) - clashctl mixin runtime + _clash_command mixin runtime ;; "") - clashctl mixin + _clash_command mixin ;; *) - clashctl mixin "$@" + _clash_command mixin "$@" ;; esac } diff --git a/scripts/core/clashctl.sh b/scripts/core/clashctl.sh index fa249f2..19d09b9 100644 --- a/scripts/core/clashctl.sh +++ b/scripts/core/clashctl.sh @@ -21,7 +21,7 @@ usage() { Usage: clashon 🚀 开启代理 clashoff ⛔ 关闭代理 - clashctl + clash 🚀 Main Path: add ➕ 添加订阅 @@ -71,20 +71,20 @@ Usage: completion 💡 导出 Bash / Zsh 补全脚本 📌 Advanced Examples: - clashctl sub update - clashctl sub list - clashctl sub enable hk - clashctl sub disable hk - clashctl sub rename hk hk-bak - clashctl sub remove hk + clash sub update + clash sub list + clash sub enable hk + clash sub disable hk + clash sub rename hk hk-bak + clash sub remove hk - clashctl relay add 多跳-示例 节点A 节点B --domain example.com - clashctl relay list + clash relay add 多跳-示例 节点A 节点B --domain example.com + clash relay list 💡 Notes: 当前编译链固定为 active-only 只处理当前 active 主订阅 - Tun 模式属于高级能力,开启前建议先执行:clashctl tun doctor + Tun 模式属于高级能力,开启前建议先执行:clash tun doctor EOF } @@ -98,7 +98,7 @@ prepare() { ensure_add_use_prerequisites() { if [ ! -x "$(yq_bin)" ]; then die_state "依赖未就绪:缺少 yq($(yq_bin))" \ - "请先执行 bash install.sh,或运行 clashctl doctor 查看缺失项" + "请先执行 bash install.sh,或运行 clash doctor 查看缺失项" fi if [ ! -d "$RUNTIME_DIR" ]; then @@ -174,7 +174,7 @@ ensure_on_path_ready() { load_system_state if [ "$SUBSCRIPTION_STATE" = "missing" ]; then - die_state "当前没有可用订阅" "clashctl add <订阅链接>" + die_state "当前没有可用订阅" "clash add <订阅链接>" fi if [ ! -s "$RUNTIME_DIR/config.yaml" ]; then @@ -184,7 +184,7 @@ ensure_on_path_ready() { fi if [ ! -s "$RUNTIME_DIR/config.yaml" ] && [ ! -s "$RUNTIME_DIR/config.last.yaml" ]; then - die_state "当前没有可启动的运行配置" "clashctl doctor" + die_state "当前没有可启动的运行配置" "clash doctor" fi ensure_runtime_ports_ready @@ -202,7 +202,7 @@ cmd_on() { local service_action="" local system_proxy_rc system_proxy_degraded="false" - trap 'rc=$?; ui_error "开启代理失败:cmd_on 在第 ${LINENO} 行执行失败:${BASH_COMMAND}(返回码:${rc})"; ui_next "clashctl logs service"; exit "$rc"' ERR + trap 'rc=$?; ui_error "开启代理失败:cmd_on 在第 ${LINENO} 行执行失败:${BASH_COMMAND}(返回码:${rc})"; ui_next "clash logs service"; exit "$rc"' ERR prepare @@ -230,22 +230,22 @@ cmd_on() { if status_is_running 2>/dev/null && ! proxy_controller_reachable 2>/dev/null; then ui_warn "检测到内核已运行但控制器不可访问,正在重启以加载当前配置" service_action="restart" - service_restart || die_state "控制器启动失败:内核重启未完成" "clashctl logs mihomo" + service_restart || die_state "控制器启动失败:内核重启未完成" "clash logs mihomo" else service_action="start" - service_start || die_state "控制器启动失败:内核启动未完成" "clashctl logs mihomo" + service_start || die_state "控制器启动失败:内核启动未完成" "clash logs mihomo" fi if ! wait_runtime_controller_ready 8; then if [ "$service_action" = "restart" ] && [ "$(runtime_backend 2>/dev/null || true)" = "script" ]; then - status_is_running 2>/dev/null || die_state "控制器启动失败:内核未运行" "clashctl logs mihomo" - ui_warn "控制器暂不可访问,继续开启本地代理;请稍后执行 clashctl doctor" + status_is_running 2>/dev/null || die_state "控制器启动失败:内核未运行" "clash logs mihomo" + ui_warn "控制器暂不可访问,继续开启本地代理;请稍后执行 clash doctor" else ui_warn "控制器未在预期时间内可访问,正在重启内核重试" - service_restart || die_state "控制器启动失败:内核重启未完成" "clashctl logs mihomo" + service_restart || die_state "控制器启动失败:内核重启未完成" "clash logs mihomo" if ! wait_runtime_controller_ready 8; then - status_is_running 2>/dev/null || die_state "控制器启动失败:内核未运行" "clashctl logs mihomo" - ui_warn "控制器暂不可访问,继续开启本地代理;请稍后执行 clashctl doctor" + status_is_running 2>/dev/null || die_state "控制器启动失败:内核未运行" "clash logs mihomo" + ui_warn "控制器暂不可访问,继续开启本地代理;请稍后执行 clash doctor" fi fi fi @@ -279,7 +279,7 @@ cmd_on() { ui_next "开机代理保持不可用;如需持久接管,请使用可写的 $(system_proxy_env_file)" else ui_warn "系统代理持久接管写入失败,仅当前 Shell 生效" - ui_next "clashctl doctor" + ui_next "clash doctor" fi fi @@ -288,9 +288,9 @@ cmd_on() { if [ "${CLASH_ALIAS_CALL:-0}" != "1" ]; then if [ "$system_proxy_degraded" = "true" ]; then - ui_warn "当前通过 clashctl 子进程执行,不能修改当前 Shell 代理变量" + ui_warn "当前通过 clash 子进程执行,不能修改当前 Shell 代理变量" else - ui_warn "当前通过 clashctl 子进程执行,已开启系统代理,但不会修改当前终端的 Shell 变量" + ui_warn "当前通过 clash 子进程执行,已开启系统代理,但不会修改当前终端的 Shell 变量" fi ui_next "当前终端如需立即生效:重新打开终端后使用 clashon,或手动 source shell 入口" ui_blank @@ -298,7 +298,7 @@ cmd_on() { if [ "$RUNTIME_STATE" = "degraded" ]; then ui_warn "代理内核已启动,但控制器暂不可访问" - ui_next "clashctl doctor" + ui_next "clash doctor" ui_blank fi @@ -326,7 +326,7 @@ cmd_off() { ui_warn "当前环境不支持清理系统代理持久块,已继续关闭运行时" else ui_warn "系统代理持久块清理失败,已继续关闭运行时" - ui_next "clashctl doctor" + ui_next "clash doctor" fi fi fi @@ -335,16 +335,16 @@ cmd_off() { stop_rc=0 service_stop || stop_rc=$? if [ "$stop_rc" -ne 0 ]; then - die_state "关闭运行时失败:运行后端 stop 返回 ${stop_rc}" "clashctl logs" + die_state "关闭运行时失败:运行后端 stop 返回 ${stop_rc}" "clash logs" fi if ! wait_runtime_stopped 8; then - die_state "关闭运行时失败:代理内核仍在运行" "clashctl logs" + die_state "关闭运行时失败:代理内核仍在运行" "clash logs" fi fi if [ "$system_proxy_cleanup_blocked" = "true" ]; then - die_state "系统代理持久块未清理:$(system_proxy_env_file)" "请使用有权限的用户执行 clashctl off,或手动清理该文件中的 clash-for-linux 代理块" + die_state "系统代理持久块未清理:$(system_proxy_env_file)" "请使用有权限的用户执行 clash off,或手动清理该文件中的 clash-for-linux 代理块" fi ui_blank @@ -418,7 +418,7 @@ cmd_ui() { controller_raw="$(status_read_controller_raw 2>/dev/null || true)" controller_addr="$(display_controller_local_addr "$controller_raw" 2>/dev/null || echo "$controller_raw")" - [ -n "${controller_addr:-}" ] && [ "$controller_addr" != "null" ] || die_state "未解析到控制器地址" "clashctl doctor" + [ -n "${controller_addr:-}" ] && [ "$controller_addr" != "null" ] || die_state "未解析到控制器地址" "clash doctor" controller_port="${controller_raw##*:}" internal_url="$(ui_internal_url "$controller_raw" 2>/dev/null || true)" @@ -462,7 +462,7 @@ cmd_ui() { ;; 不可访问) ui_warn "控制器当前不可访问" - ui_next "clashctl doctor" + ui_next "clash doctor" ui_blank return 1 ;; @@ -474,7 +474,7 @@ cmd_ui() { ;; *) ui_warn "控制器状态未知" - ui_next "clashctl doctor" + ui_next "clash doctor" ui_blank return 1 ;; @@ -834,11 +834,11 @@ EOF case "$kind" in bind_denied) - mixed_port_bind_recommendation_emit "$style" "$n" "当前 ${env_text} 对端口 ${port:-unknown} 的可绑定性受限;优先修改 .env 中 MIXED_PORT 后执行 clashctl config regen" + mixed_port_bind_recommendation_emit "$style" "$n" "当前 ${env_text} 对端口 ${port:-unknown} 的可绑定性受限;优先修改 .env 中 MIXED_PORT 后执行 clash config regen" n=$((n + 1)) ;; address_in_use) - mixed_port_bind_recommendation_emit "$style" "$n" "mixed-port ${port:-unknown} 存在端口冲突;先检查占用进程,或修改 .env 中 MIXED_PORT 后执行 clashctl config regen" + mixed_port_bind_recommendation_emit "$style" "$n" "mixed-port ${port:-unknown} 存在端口冲突;先检查占用进程,或修改 .env 中 MIXED_PORT 后执行 clash config regen" n=$((n + 1)) ;; *) @@ -847,7 +847,7 @@ EOF ;; esac - mixed_port_bind_recommendation_emit "$style" "$n" "保留日志线索:clashctl logs mihomo" + mixed_port_bind_recommendation_emit "$style" "$n" "保留日志线索:clash logs mihomo" } mixed_port_bind_next_action() { @@ -858,13 +858,13 @@ mixed_port_bind_next_action() { case "$kind" in bind_denied) - echo "修改 .env 中 MIXED_PORT 后执行 clashctl config regen" + echo "修改 .env 中 MIXED_PORT 后执行 clash config regen" ;; address_in_use) - echo "检查 ${port:-mixed-port} 占用,或修改 .env 中 MIXED_PORT 后执行 clashctl config regen" + echo "检查 ${port:-mixed-port} 占用,或修改 .env 中 MIXED_PORT 后执行 clash config regen" ;; bind_failed) - echo "clashctl logs mihomo" + echo "clash logs mihomo" ;; *) return 1 @@ -1043,40 +1043,40 @@ system_state_default_action() { case "$SYSTEM_STATE" in stopped) if [ "${BIND_FAILURE_STATE:-none}" != "none" ]; then - mixed_port_bind_next_action 2>/dev/null || echo "clashctl logs mihomo" + mixed_port_bind_next_action 2>/dev/null || echo "clash logs mihomo" return 0 fi if [ "$SUBSCRIPTION_STATE" = "missing" ]; then - echo "clashctl add <订阅链接>" + echo "clash add <订阅链接>" else echo "clashon" fi ;; broken) if [ "${BIND_FAILURE_STATE:-none}" != "none" ]; then - mixed_port_bind_next_action 2>/dev/null || echo "clashctl logs mihomo" + mixed_port_bind_next_action 2>/dev/null || echo "clash logs mihomo" return 0 fi if [ "$SUBSCRIPTION_STATE" = "missing" ]; then - echo "clashctl add <订阅链接>" + echo "clash add <订阅链接>" else - echo "clashctl doctor" + echo "clash doctor" fi ;; degraded) if [ "$RUNTIME_STATE" = "degraded" ]; then - echo "clashctl doctor" + echo "clash doctor" else - echo "clashctl status --verbose" + echo "clash status --verbose" fi ;; ready) - echo "clashctl select" + echo "clash select" ;; *) - echo "clashctl status --verbose" + echo "clash status --verbose" ;; esac } @@ -1125,31 +1125,31 @@ system_state_recommendation_lines() { case "$SYSTEM_STATE" in ready) - echo "1. clashctl select" - echo "2. clashctl ls" + echo "1. clash select" + echo "2. clash ls" ;; stopped) if [ "$SUBSCRIPTION_STATE" = "missing" ]; then - echo "1. clashctl add <订阅链接>" + echo "1. clash add <订阅链接>" else echo "1. clashon" - echo "2. clashctl status" + echo "2. clash status" fi ;; degraded) - echo "1. clashctl doctor" - echo "2. clashctl status --verbose" + echo "1. clash doctor" + echo "2. clash status --verbose" ;; broken) if [ "$SUBSCRIPTION_STATE" = "missing" ]; then - echo "1. clashctl add <订阅链接>" + echo "1. clash add <订阅链接>" else - echo "1. clashctl doctor" - echo "2. clashctl ls" + echo "1. clash doctor" + echo "2. clash ls" fi ;; *) - echo "1. clashctl status --verbose" + echo "1. clash status --verbose" ;; esac } @@ -1340,7 +1340,7 @@ print_config_apply_feedback() { build_status="$(status_build_effective_status 2>/dev/null || true)" build_applied="$(status_runtime_build_applied 2>/dev/null || true)" config_source="$(status_runtime_config_source 2>/dev/null || true)" - next_action="$(system_state_default_action 2>/dev/null || echo 'clashctl status')" + next_action="$(system_state_default_action 2>/dev/null || echo 'clash status')" echo echo "🧩 配置变更已处理" @@ -1418,7 +1418,7 @@ print_add_feedback() { echo "✔ 已添加订阅并设为当前主订阅:$name" [ -n "${url:-}" ] && echo "📡 URL:$url" - echo "📋 最新订阅列表(clashctl ls):" + echo "📋 最新订阅列表(clash ls):" } print_use_context() { @@ -1465,7 +1465,7 @@ print_use_feedback() { main_feedback_build_mode main_feedback_runtime_state - ui_next "clashctl select 选择节点" + ui_next "clash select 选择节点" ui_blank } @@ -1481,14 +1481,14 @@ print_select_context() { if ! proxy_controller_reachable 2>/dev/null; then ui_kv "❗" "当前节点" "控制器不可访问" - ui_next "clashctl doctor" + ui_next "clash doctor" ui_blank return 1 fi if [ "$(proxy_group_count 2>/dev/null || echo 0)" -le 0 ]; then ui_kv "❗" "当前节点" "暂无可切换策略组" - ui_next "clashctl status --verbose" + ui_next "clash status --verbose" ui_blank return 1 fi @@ -1522,19 +1522,19 @@ print_select_feedback() { fi main_feedback_runtime_state - ui_next "clashctl status" + ui_next "clash status" ui_blank } select_usage() { cat <<'EOF' Usage: - clashctl select - clashctl select <策略组> <节点> + clash select + clash select <策略组> <节点> Examples: - clashctl select - clashctl select "🚀 节点选择" "日本 01" + clash select + clash select "🚀 节点选择" "日本 01" Notes: 不带参数时进入交互选择。 @@ -1545,13 +1545,13 @@ EOF mode_usage() { cat <<'EOF' Usage: - clashctl mode - clashctl mode status - clashctl mode rule|global|direct + clash mode + clash mode status + clash mode rule|global|direct Examples: - clashctl mode global - clashctl mode rule + clash mode global + clash mode rule Notes: global 模式使用 GLOBAL 策略组当前选择的策略组或节点。 @@ -1579,14 +1579,14 @@ cmd_mode() { ;; esac - [ "$#" -le 1 ] || die_usage "mode 参数不合法" "clashctl mode [status|rule|global|direct]" + [ "$#" -le 1 ] || die_usage "mode 参数不合法" "clash mode [status|rule|global|direct]" action="$(printf '%s' "$action" | tr '[:upper:]' '[:lower:]')" case "$action" in status|rule|global|direct) ;; *) - die_usage "未知路由模式:$action" "clashctl mode [status|rule|global|direct]" + die_usage "未知路由模式:$action" "clash mode [status|rule|global|direct]" ;; esac @@ -1597,7 +1597,7 @@ cmd_mode() { fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi if [ "$action" != "status" ]; then @@ -1625,11 +1625,11 @@ cmd_mode() { if [ -n "${global_current:-}" ]; then ui_kv "🚀" "GLOBAL 当前选择" "$global_current" else - ui_warn "未读取到 GLOBAL 当前选择,请执行 clashctl select 检查" + ui_warn "未读取到 GLOBAL 当前选择,请执行 clash select 检查" fi - ui_next "clashctl select \"GLOBAL\" \"<策略组或节点>\"" + ui_next "clash select \"GLOBAL\" \"<策略组或节点>\"" else - ui_next "clashctl status" + ui_next "clash status" fi ui_blank } @@ -1637,13 +1637,13 @@ cmd_mode() { test_usage() { cat <<'EOF' Usage: - clashctl test - clashctl test <策略组> + clash test + clash test <策略组> clashtest [策略组] Examples: - clashctl test - clashctl test "节点选择" + clash test + clash test "节点选择" Notes: 默认测试当前路由模式对应的策略组当前选择。 @@ -1682,7 +1682,7 @@ cmd_test() { ;; esac - [ "$#" -le 1 ] || die_usage "test 参数不合法" "clashctl test [策略组]" + [ "$#" -le 1 ] || die_usage "test 参数不合法" "clash test [策略组]" prepare @@ -1691,7 +1691,7 @@ cmd_test() { fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi mode="$(proxy_mode_current 2>/dev/null || true)" @@ -1736,7 +1736,7 @@ cmd_test() { if [ "$failed" -gt 0 ]; then ui_warn "${failed} 个目标不可访问" - ui_next "clashctl select" + ui_next "clash select" ui_blank return 1 fi @@ -1757,7 +1757,7 @@ print_sub_enable_feedback() { ui_kv "❤️" "当前健康" "$health / fail=$fail_count" main_feedback_build_mode main_feedback_runtime_state - ui_next "clashctl health ${name}" + ui_next "clash health ${name}" ui_blank } @@ -1772,10 +1772,10 @@ print_sub_disable_feedback() { if [ "$name" = "$active" ]; then ui_kv "🚨" "当前主订阅" "已被禁用" - ui_next "clashctl use" + ui_next "clash use" else ui_kv "🧩" "当前主订阅" "${active:-未设置}" - ui_next "clashctl status" + ui_next "clash status" fi main_feedback_build_mode @@ -1792,7 +1792,7 @@ print_sub_rename_feedback() { ui_kv "📡" "新名称" "$new_name" main_feedback_build_mode main_feedback_runtime_state - ui_next "clashctl ls" + ui_next "clash ls" ui_blank } @@ -1807,7 +1807,7 @@ print_sub_remove_feedback() { ui_kv "🚩" "当前主订阅" "${active:-未设置}" main_feedback_build_mode main_feedback_runtime_state - ui_next "clashctl ls" + ui_next "clash ls" ui_blank } @@ -1817,7 +1817,7 @@ print_config_kernel_feedback() { ui_title "🚀 运行内核已切换" ui_kv "🚀" "当前内核" "$kernel" main_feedback_runtime_state - ui_next "clashctl status" + ui_next "clash status" ui_blank } @@ -1906,7 +1906,7 @@ print_tun_container_gate_feedback() { ui_kv "❗" "容器裁决" "高风险,已阻断开启" [ -n "${reason:-}" ] && ui_kv "❗" "阻断原因" "$reason" tun_container_runtime_hint_lines | sed 's/^/ /' || true - ui_next "查看完整诊断:clashctl tun doctor" + ui_next "查看完整诊断:clash tun doctor" ui_blank ;; *) @@ -1917,11 +1917,11 @@ print_tun_container_gate_feedback() { if ! tun_kernel_is_recommended 2>/dev/null; then ui_warn "$(tun_kernel_support_reason 2>/dev/null || echo '当前内核不适合作为 Tun 主支持内核')" - ui_next "如需最稳妥 Tun 体验,先执行:clashctl config kernel mihomo" + ui_next "如需最稳妥 Tun 体验,先执行:clash config kernel mihomo" fi if [ "$mode" != "container-risky" ]; then - ui_next "clashctl tun doctor" + ui_next "clash tun doctor" ui_blank fi } @@ -1971,7 +1971,7 @@ print_tun_on_feedback() { ui_kv "$status_icon" "当前状态" "$status_text" echo "💡 $detail" - ui_next "查看完整证据:clashctl tun doctor" + ui_next "查看完整证据:clash tun doctor" ui_blank } @@ -1995,7 +1995,7 @@ print_tun_off_feedback() { ;; esac - ui_next "clashctl tun doctor" + ui_next "clash tun doctor" ui_blank } @@ -2349,25 +2349,25 @@ connectivity_issue_text() { connectivity_next_action() { case "$(connectivity_issue_code)" in ok|local_proxy_ready_system_proxy_on) - echo "clashctl select" + echo "clash select" ;; runtime_stopped) echo "clashon" ;; mixed_port_bind_denied|mixed_port_address_in_use|mixed_port_bind_failed) - mixed_port_bind_next_action 2>/dev/null || echo "clashctl logs mihomo" + mixed_port_bind_next_action 2>/dev/null || echo "clash logs mihomo" ;; controller_unreachable) - echo "clashctl doctor" + echo "clash doctor" ;; config_invalid) - echo "clashctl doctor" + echo "clash doctor" ;; subscription_unhealthy) - echo "clashctl ls" + echo "clash ls" ;; proxy_control_broken) - echo "clashctl status --verbose" + echo "clash status --verbose" ;; system_proxy_unsupported|local_proxy_ready_system_proxy_unsupported) echo "浏览器/应用手动设置代理,或在当前 Shell 使用 clashon" @@ -2379,7 +2379,7 @@ connectivity_next_action() { echo "clashoff && clashon" ;; *) - echo "clashctl doctor" + echo "clash doctor" ;; esac } @@ -2648,7 +2648,7 @@ print_status_summary_compact() { user_connectivity="$(connectivity_issue_text)" user_risk="$(status_user_risk_text)" current_proxy_brief="$(status_current_proxy_brief)" - next_action="$(connectivity_next_action 2>/dev/null || system_state_default_action 2>/dev/null || echo 'clashctl status --verbose')" + next_action="$(connectivity_next_action 2>/dev/null || system_state_default_action 2>/dev/null || echo 'clash status --verbose')" system_proxy_text="$(persistent_system_proxy_text 2>/dev/null || echo unknown)" if [ -f "$(runtime_dashboard_dir)/index.html" ]; then dashboard_text="已部署" @@ -2681,8 +2681,8 @@ print_status_summary_compact() { echo "📡 当前订阅:${current_active:-未设置}" echo "🚀 当前节点:$current_proxy_brief" echo "🚨 当前风险:$user_risk" - if [ "$next_action" = "clashctl select" ]; then - echo "👉 clashctl select 切换节点" + if [ "$next_action" = "clash select" ]; then + echo "👉 clash select 切换节点" else echo "👉 下一步:$next_action" fi @@ -2727,7 +2727,7 @@ print_status_summary_compact() { echo echo "🧩 安装验证:$(status_install_verify_brief)" - echo "💡 更多细节:clashctl status --verbose" + echo "💡 更多细节:clash status --verbose" echo } @@ -2796,7 +2796,7 @@ print_status_summary_verbose() { user_connectivity="$(connectivity_issue_text)" user_risk="$(status_user_risk_text)" current_proxy_brief="$(status_current_proxy_brief)" - next_action="$(connectivity_next_action 2>/dev/null || system_state_default_action 2>/dev/null || echo 'clashctl status --verbose')" + next_action="$(connectivity_next_action 2>/dev/null || system_state_default_action 2>/dev/null || echo 'clash status --verbose')" install_backend_text="$(status_runtime_backend_text)" install_container_text="$(status_container_mode_text)" install_verify_text="$(status_install_verify_brief)" @@ -2842,8 +2842,8 @@ print_status_summary_verbose() { echo "📡 当前订阅:${current_active:-未设置}" echo "🚀 当前节点:$current_proxy_brief" echo "🚨 当前风险:$user_risk" - if [ "$next_action" = "clashctl select" ]; then - echo "👉 clashctl select 切换节点" + if [ "$next_action" = "clash select" ]; then + echo "👉 clash select 切换节点" else echo "👉 下一步:$next_action" fi @@ -3007,7 +3007,7 @@ cmd_status() { print_status_summary_compact ;; *) - die_usage "未知的 status 参数:$1" "clashctl status [--verbose]" + die_usage "未知的 status 参数:$1" "clash status [--verbose]" ;; esac } @@ -3020,11 +3020,11 @@ cmd_status_next() { boot_usage() { cat </dev/null || echo unknown)" \ "如需开机自启,请使用 systemd / systemd-user 后端" fi - service_autostart_enable || die_state "内核开机自启开启失败" "clashctl doctor" + service_autostart_enable || die_state "内核开机自启开启失败" "clash doctor" success "内核开机自启已开启" ;; off) @@ -3054,7 +3054,7 @@ cmd_boot_runtime() { write_runtime_value "RUNTIME_BOOT_AUTOSTART_EXPLICIT" "true" ui_warn "当前后端不支持内核开机自启,已记录为关闭:$(runtime_backend 2>/dev/null || echo unknown)" else - service_autostart_disable || die_state "内核开机自启关闭失败" "clashctl doctor" + service_autostart_disable || die_state "内核开机自启关闭失败" "clash doctor" success "内核开机自启已关闭" fi ;; @@ -3063,7 +3063,7 @@ cmd_boot_runtime() { return 0 ;; *) - die_usage "未知的 boot runtime 参数:$1" "clashctl boot runtime on|off|status" + die_usage "未知的 boot runtime 参数:$1" "clash boot runtime on|off|status" ;; esac @@ -3080,12 +3080,12 @@ cmd_boot_proxy() { ui_warn "script 后端不会开机启动内核;仅保持系统代理变量可能在重启后指向未运行的本地端口" fi boot_proxy_keep_enable || die_state "开机代理保持开启失败:当前环境不支持写入 $(system_proxy_env_file)" \ - "请检查权限,或执行 clashctl doctor" + "请检查权限,或执行 clash doctor" success "开机代理保持已开启" ;; off) boot_proxy_keep_disable || die_state "开机代理保持关闭失败:无法清理 $(system_proxy_env_file)" \ - "请检查权限,或执行 clashctl doctor" + "请检查权限,或执行 clash doctor" success "开机代理保持已关闭" ;; status) @@ -3093,7 +3093,7 @@ cmd_boot_proxy() { return 0 ;; *) - die_usage "未知的 boot proxy 参数:$1" "clashctl boot proxy on|off|status" + die_usage "未知的 boot proxy 参数:$1" "clash boot proxy on|off|status" ;; esac @@ -3107,27 +3107,27 @@ cmd_boot() { on) if ! service_autostart_supported; then die_state "当前后端不支持开机自动进入代理状态:$(runtime_backend 2>/dev/null || echo unknown)" \ - "script 后端只能手动启动;可执行 clashctl boot proxy off 清理开机代理保持" + "script 后端只能手动启动;可执行 clash boot proxy off 清理开机代理保持" fi boot_proxy_keep_enable || die_state "开机代理保持开启失败:当前环境不支持写入 $(system_proxy_env_file)" \ - "请检查权限,或执行 clashctl doctor" + "请检查权限,或执行 clash doctor" if ! service_autostart_enable; then boot_proxy_keep_disable >/dev/null 2>&1 || true - die_state "内核开机自启开启失败" "clashctl doctor" + die_state "内核开机自启开启失败" "clash doctor" fi success "开机代理接管已开启" print_boot_status ;; off) if service_autostart_supported; then - service_autostart_disable || die_state "内核开机自启关闭失败" "clashctl doctor" + service_autostart_disable || die_state "内核开机自启关闭失败" "clash doctor" else write_runtime_value "RUNTIME_BOOT_AUTOSTART" "false" write_runtime_value "RUNTIME_BOOT_AUTOSTART_EXPLICIT" "true" ui_warn "当前后端不支持内核开机自启,跳过服务 disable:$(runtime_backend 2>/dev/null || echo unknown)" fi boot_proxy_keep_disable || die_state "开机代理保持关闭失败:无法清理 $(system_proxy_env_file)" \ - "请检查权限,或执行 clashctl doctor" + "请检查权限,或执行 clash doctor" success "开机代理接管已关闭" print_boot_status ;; @@ -3146,7 +3146,7 @@ cmd_boot() { boot_usage ;; *) - die_usage "未知的 boot 子命令:$1" "clashctl boot on|off|status" + die_usage "未知的 boot 子命令:$1" "clash boot on|off|status" ;; esac } @@ -3277,30 +3277,30 @@ cmd_ui_help_summary() { echo "〽️ 常用命令" printf ' %-18s %s\n' "clashon" "🚀 开启代理" printf ' %-18s %s\n' "clashoff" "⛔ 关闭代理" - printf ' %-18s %s\n' "clashctl select" "💫 选择节点" - printf ' %-18s %s\n' "clashctl mode" "🧭 查看或切换路由模式" + printf ' %-18s %s\n' "clash select" "💫 选择节点" + printf ' %-18s %s\n' "clash mode" "🧭 查看或切换路由模式" printf ' %-18s %s\n' "clashtest" "🌐 测试当前节点连通性" echo "🕹️ 控制台" printf ' %-18s %s\n' "clashui" "🕹️ 查看 Web 控制台" printf ' %-18s %s\n' "clashsecret" "🔑 查看或设置 Web 密钥" echo "📦 订阅" - printf ' %-18s %s\n' "clashctl add" "➕ 添加订阅" - printf ' %-18s %s\n' "clashctl add local" "➕ 从 runtime/subscriptions 导入本地订阅" - printf ' %-18s %s\n' "clashctl use" "💱 切换订阅" - printf ' %-18s %s\n' "clashctl ls" "📜 查看订阅列表" + printf ' %-18s %s\n' "clash add" "➕ 添加订阅" + printf ' %-18s %s\n' "clash add local" "➕ 从 runtime/subscriptions 导入本地订阅" + printf ' %-18s %s\n' "clash use" "💱 切换订阅" + printf ' %-18s %s\n' "clash ls" "📜 查看订阅列表" echo "📌 高级" - printf ' %-18s %s\n' "clashctl lan" "🏠 局域网代理管理" - printf ' %-18s %s\n' "clashctl tun" "🧪 Tun 模式管理" - printf ' %-18s %s\n' "clashctl mixin" "🧩 Mixin 配置管理" - printf ' %-18s %s\n' "clashctl sub" "🧩 订阅高级管理(启用 / 禁用 / 重命名 / 删除)" - printf ' %-18s %s\n' "clashctl upgrade" "🚀 升级当前或指定内核" - printf ' %-18s %s\n' "clashctl update" "🔄 更新项目代码" - printf ' %-18s %s\n' "clashctl completion" "💡 导出 Bash / Zsh 补全脚本" + printf ' %-18s %s\n' "clash lan" "🏠 局域网代理管理" + printf ' %-18s %s\n' "clash tun" "🧪 Tun 模式管理" + printf ' %-18s %s\n' "clash mixin" "🧩 Mixin 配置管理" + printf ' %-18s %s\n' "clash sub" "🧩 订阅高级管理(启用 / 禁用 / 重命名 / 删除)" + printf ' %-18s %s\n' "clash upgrade" "🚀 升级当前或指定内核" + printf ' %-18s %s\n' "clash update" "🔄 更新项目代码" + printf ' %-18s %s\n' "clash completion" "💡 导出 Bash / Zsh 补全脚本" echo "📜 日志" - printf ' %-18s %s\n' "clashctl doctor" "🩺 诊断面板" - printf ' %-18s %s\n' "clashctl log/logs" "📜 查看日志" + printf ' %-18s %s\n' "clash doctor" "🩺 诊断面板" + printf ' %-18s %s\n' "clash log/logs" "📜 查看日志" echo - echo "💡 显示更多帮助命令:clashctl -h" + echo "💡 显示更多帮助命令:clash -h" } logs_mihomo() { @@ -3358,7 +3358,7 @@ systemd_unit_has_stale_runtime_template() { [ -f "$unit_file" ] || return 1 - grep -Eq '^[[:space:]]*Type=forking([[:space:]]|$)|^[[:space:]]*PIDFile=|clashctl[[:space:]]+start-direct' "$unit_file" + grep -Eq '^[[:space:]]*Type=forking([[:space:]]|$)|^[[:space:]]*PIDFile=|(clash|clashctl)[[:space:]]+start-direct' "$unit_file" } systemd_unit_stale_runtime_template_warning() { @@ -3404,7 +3404,7 @@ cmd_logs() { logs_service ;; *) - die "用法:clashctl log|logs [mihomo|subconverter|service]" + die "用法:clash log|logs [mihomo|subconverter|service]" ;; esac } @@ -3577,7 +3577,7 @@ doctor_tun_system_proxy() { if [ "$tun_state" = "true" ] && [ "$system_proxy_state" = "on" ]; then doctor_warn "Tun 模式与系统代理同时开启,可能造成流量接管路径重复或排障混淆" - echo " 建议:clashctl tun on-proxy-off" + echo " 建议:clash tun on-proxy-off" fi } @@ -4095,11 +4095,17 @@ doctor_install_verify() { doctor_print_title "安装验证检查" case "$(install_verify_command_ready 2>/dev/null || true)" in - true) doctor_ok "clashctl 命令入口可用" ;; - false) doctor_fail "clashctl 命令入口不可用" ;; + true) doctor_ok "管理命令入口可用" ;; + false) doctor_fail "管理命令入口不可用" ;; *) doctor_warn "未记录命令入口验证结果" ;; esac + if clash_entry_is_managed; then + doctor_ok "clash 主命令入口可用" + else + doctor_warn "clash 主命令入口未由本项目接管(可能存在同名命令)" + fi + case "$(install_verify_config_ready 2>/dev/null || true)" in true) doctor_ok "安装后配置已就绪" ;; false) doctor_fail "安装后配置未就绪" ;; @@ -4255,14 +4261,14 @@ cmd_dev() { echo echo "🐱 开发重置完成" echo "🧩 保留内容:项目目录、已下载依赖、调试环境" - echo "👉 下一步:重新执行 install.sh 或 clashctl status" + echo "👉 下一步:重新执行 install.sh 或 clash status" echo ;; "") - echo "📜 用法:clashctl dev reset" + echo "📜 用法:clash dev reset" ;; *) - die_usage "未知的 dev 子命令:$1" "clashctl dev reset" + die_usage "未知的 dev 子命令:$1" "clash dev reset" ;; esac } @@ -4285,7 +4291,7 @@ cmd_config_show() { ui_kv "🐱" "最近构建" "${build_status:-unknown}${build_time:+ @ ${build_time}}" ui_kv "🧩" "配置来源" "${config_source:-unknown}" ui_blank - ui_next "clashctl status" + ui_next "clash status" ui_blank } @@ -4307,7 +4313,7 @@ cmd_config() { ;; kernel) shift || true - [ -n "${1:-}" ] || die_usage "内核类型不能为空" "clashctl config kernel " + [ -n "${1:-}" ] || die_usage "内核类型不能为空" "clash config kernel " write_runtime_kernel_type "$1" resolve_runtime_kernel regenerate_config @@ -4318,20 +4324,20 @@ cmd_config() { "") ui_title "🧩 配置编译管理" echo "📜 用法:" - echo " clashctl config show" - echo " clashctl config explain" - echo " clashctl config regen" - echo " clashctl config kernel " + echo " clash config show" + echo " clash config explain" + echo " clash config regen" + echo " clash config kernel " echo echo "🧩 说明:" echo " 当前编译链固定为 active-only" echo " 只处理当前 active 主订阅" echo - ui_next "clashctl config show" + ui_next "clash config show" ui_blank ;; *) - die_usage "未知的 config 子命令:$1" "clashctl config" + die_usage "未知的 config 子命令:$1" "clash config" ;; esac } @@ -4382,17 +4388,17 @@ cmd_lan() { ;; status) print_lan_status - ui_next "clashctl lan on" + ui_next "clash lan on" ui_blank ;; -h|--help|help|"") echo "📜 用法:" - echo " clashctl lan status" - echo " clashctl lan on" - echo " clashctl lan off" + echo " clash lan status" + echo " clash lan on" + echo " clash lan off" ;; *) - die_usage "未知的 lan 子命令:$action" "clashctl lan on|off|status" + die_usage "未知的 lan 子命令:$action" "clash lan on|off|status" ;; esac } @@ -4403,7 +4409,7 @@ print_profile_use_feedback() { ui_title "🔧 Profile 已切换" ui_kv "🔧" "当前 Profile" "$profile" main_feedback_runtime_state - ui_next "clashctl status" + ui_next "clash status" ui_blank } @@ -4486,11 +4492,11 @@ cmd_mixin_show() { else cat "$file" if mixin_config_has_secret_override "$file"; then - ui_warn "检测到 override.secret:该字段已忽略,请改用 clashctl secret <密钥>" + ui_warn "检测到 override.secret:该字段已忽略,请改用 clash secret <密钥>" fi fi ui_blank - ui_next "clashctl mixin edit" + ui_next "clash mixin edit" ui_blank } @@ -4586,7 +4592,7 @@ cmd_mixin_edit() { echo "🟡 Mixin 已写入(下次启动生效)" fi - echo "👉 下一步:clashctl mixin runtime" + echo "👉 下一步:clash mixin runtime" echo } @@ -4603,7 +4609,7 @@ cmd_mixin_raw() { echo cat "$file" echo - echo "👉 下一步:clashctl mixin runtime" + echo "👉 下一步:clash mixin runtime" echo } @@ -4622,7 +4628,7 @@ cmd_mixin_runtime() { echo cat "$file" echo - echo "👉 下一步:clashctl status" + echo "👉 下一步:clash status" echo } @@ -4643,30 +4649,30 @@ cmd_mixin() { help|-h|--help) ui_title "🧩 Mixin 配置管理" echo "📜 用法:" - echo " clashctl mixin" - echo " clashctl mixin edit" - echo " clashctl mixin raw" - echo " clashctl mixin runtime" + echo " clash mixin" + echo " clash mixin edit" + echo " clash mixin raw" + echo " clash mixin runtime" echo echo "🧩 说明:" echo " mixin 用于补充、覆盖或前后置合并原始订阅配置" echo " runtime 展示当前内核真正加载的最终配置" echo echo "💡 常用动作:" - echo " clashctl mixin" - echo " clashctl mixin edit" - echo " clashctl mixin runtime" + echo " clash mixin" + echo " clash mixin edit" + echo " clash mixin runtime" echo echo "⚡ 快捷参数:" - echo " clashctl mixin -e" - echo " clashctl mixin -c" - echo " clashctl mixin -r" + echo " clash mixin -e" + echo " clash mixin -c" + echo " clash mixin -r" echo - ui_next "clashctl mixin" + ui_next "clash mixin" ui_blank ;; *) - die_usage "未知的 mixin 子命令:$1" "clashctl mixin" + die_usage "未知的 mixin 子命令:$1" "clash mixin" ;; esac } @@ -4724,14 +4730,14 @@ cmd_relay_add() { [ -x "$(yq_bin)" ] || die_state "依赖未就绪:缺少 yq($(yq_bin))" "请先执行 bash install.sh" name="${1:-}" - [ -n "${name:-}" ] || die_usage "缺少多跳名称" "clashctl relay add <名称> <节点A> <节点B> [更多节点] [--domain 域名|--match]" + [ -n "${name:-}" ] || die_usage "缺少多跳名称" "clash relay add <名称> <节点A> <节点B> [更多节点] [--domain 域名|--match]" shift || true while [ $# -gt 0 ]; do case "$1" in --domain) shift || true - [ -n "${1:-}" ] || die_usage "--domain 缺少域名" "clashctl relay add <名称> <节点A> <节点B> --domain example.com" + [ -n "${1:-}" ] || die_usage "--domain 缺少域名" "clash relay add <名称> <节点A> <节点B> --domain example.com" domain="$1" ;; --match) @@ -4746,7 +4752,7 @@ cmd_relay_add() { break ;; --*) - die_usage "未知 relay add 参数:$1" "clashctl relay add <名称> <节点A> <节点B> [--domain 域名|--match]" + die_usage "未知 relay add 参数:$1" "clash relay add <名称> <节点A> <节点B> [--domain 域名|--match]" ;; *) nodes+=("$1") @@ -4755,10 +4761,10 @@ cmd_relay_add() { shift || true done - [ "${#nodes[@]}" -ge 2 ] || die_usage "多跳至少需要两个节点" "clashctl relay add <名称> <节点A> <节点B>" + [ "${#nodes[@]}" -ge 2 ] || die_usage "多跳至少需要两个节点" "clash relay add <名称> <节点A> <节点B>" if [ -n "${domain:-}" ] && [ "$match_mode" = "true" ]; then - die_usage "--domain 与 --match 只能选择一个" "clashctl relay add <名称> <节点A> <节点B> [--domain 域名|--match]" + die_usage "--domain 与 --match 只能选择一个" "clash relay add <名称> <节点A> <节点B> [--domain 域名|--match]" fi if [ -n "${domain:-}" ]; then @@ -4793,7 +4799,7 @@ cmd_relay_add() { [ -n "${rule:-}" ] && ui_kv "📜" "新增规则" "$rule" relay_apply_mixin_change - echo "👉 下一步:clashctl relay list" + echo "👉 下一步:clash relay list" echo } @@ -4817,7 +4823,7 @@ cmd_relay_list() { printf '%s\n' "$output" else echo "当前没有通过 mixin 配置的多跳组" - ui_next "clashctl relay add 多跳-示例 节点A 节点B --domain example.com" + ui_next "clash relay add 多跳-示例 节点A 节点B --domain example.com" fi ui_blank } @@ -4828,7 +4834,7 @@ cmd_relay_remove() { [ -x "$(yq_bin)" ] || die_state "依赖未就绪:缺少 yq($(yq_bin))" "请先执行 bash install.sh" name="${1:-}" - [ -n "${name:-}" ] || die_usage "缺少多跳名称" "clashctl relay remove <名称>" + [ -n "${name:-}" ] || die_usage "缺少多跳名称" "clash relay remove <名称>" file="$(ensure_relay_mixin_file)" RELAY_NAME="$name" "$(yq_bin)" eval -i ' @@ -4842,7 +4848,7 @@ cmd_relay_remove() { ui_kv "🔗" "多跳名称" "$name" relay_apply_mixin_change - echo "👉 下一步:clashctl relay list" + echo "👉 下一步:clash relay list" echo } @@ -4862,13 +4868,13 @@ cmd_relay() { help|-h|--help) ui_title "🔗 多跳节点管理" echo "📜 用法:" - echo " clashctl relay add <名称> <节点A> <节点B> [更多节点] [--domain 域名|--match]" - echo " clashctl relay list" - echo " clashctl relay remove <名称>" + echo " clash relay add <名称> <节点A> <节点B> [更多节点] [--domain 域名|--match]" + echo " clash relay list" + echo " clash relay remove <名称>" echo echo "示例:" - echo " clashctl relay add 多跳-示例 节点A 节点B --domain example.com" - echo " clashctl relay add 全局多跳 节点A 节点B --match" + echo " clash relay add 多跳-示例 节点A 节点B --domain example.com" + echo " clash relay add 全局多跳 节点A 节点B --match" echo echo "说明:" echo " add 会写入 runtime/mixin.yaml(兼容读取 config/mixin.yaml),并重新生成运行配置" @@ -4876,7 +4882,7 @@ cmd_relay() { echo " 节点名称必须与订阅生成的节点名完全一致" ;; *) - die_usage "未知的 relay 子命令:$1" "clashctl relay help" + die_usage "未知的 relay 子命令:$1" "clash relay help" ;; esac } @@ -5010,9 +5016,9 @@ doctor_recommendation_lines() { if ! runtime_config_exists; then if [ -n "$(subscription_url 2>/dev/null || true)" ]; then - echo "💡 clashctl config regen" + echo "💡 clash config regen" else - echo "💡 clashctl add <订阅链接>" + echo "💡 clash add <订阅链接>" fi return 0 fi @@ -5026,27 +5032,27 @@ doctor_recommendation_lines() { fi if ! proxy_controller_reachable 2>/dev/null; then - echo "💡 clashctl logs service" - echo "💡 clashctl off && clashon" + echo "💡 clash logs service" + echo "💡 clash off && clashon" return 0 fi if [ -n "${active_sub:-}" ] && ! active_subscription_enabled 2>/dev/null; then - echo "💡 clashctl use" - echo "💡 clashctl ls" + echo "💡 clash use" + echo "💡 clash ls" return 0 fi if [ "$(status_build_last_status 2>/dev/null || true)" = "failed" ]; then - echo "💡 clashctl config regen" - echo "💡 clashctl doctor" + echo "💡 clash config regen" + echo "💡 clash doctor" return 0 fi if [ "$(tun_enabled 2>/dev/null || echo false)" = "true" ] \ && [ "$(system_proxy_status 2>/dev/null || echo off)" = "on" ]; then - echo "💡 clashctl tun on-proxy-off" - echo "💡 如需恢复普通系统代理模式:clashctl tun off-proxy-on" + echo "💡 clash tun on-proxy-off" + echo "💡 如需恢复普通系统代理模式:clash tun off-proxy-on" return 0 fi @@ -5061,8 +5067,8 @@ doctor_recommendation_lines() { echo "💡 clashoff && clashon" ;; *) - echo "💡 clashctl status" - echo "💡 clashctl select" + echo "💡 clash status" + echo "💡 clash select" ;; esac } @@ -5200,7 +5206,7 @@ print_controller_secret_apply_feedback() { ui_kv "🐱" "状态" "将在下次启动时生效" fi else - ui_warn "运行时配置暂未同步:缺少 yq 或写入失败,请稍后执行 clashctl config regen" + ui_warn "运行时配置暂未同步:缺少 yq 或写入失败,请稍后执行 clash config regen" ui_kv "🐱" "状态" "密钥已写入 .env,运行时配置同步后生效" fi @@ -5218,7 +5224,7 @@ cmd_secret() { return 0 fi - [ "$#" -eq 1 ] || die_usage "secret 参数不合法" "clashctl secret <密钥>" + [ "$#" -eq 1 ] || die_usage "secret 参数不合法" "clash secret <密钥>" new_secret="$1" set_controller_secret "$new_secret" @@ -5263,9 +5269,9 @@ cmd_tun_status() { fi if [ "$enabled" = "true" ]; then - echo "👉 下一步:clashctl tun doctor" + echo "👉 下一步:clash tun doctor" else - echo "👉 下一步:clashctl tun on" + echo "👉 下一步:clash tun on" fi echo } @@ -5315,7 +5321,7 @@ cmd_tun_on() { echo "❗ Tun 模式无法开启" echo "🚨 原因:当前环境不满足基础 Tun 条件" echo "💡 若已通过 setcap 授权 mihomo,请确认 getcap 已安装并重试" - echo "👉 下一步:clashctl tun doctor" + echo "👉 下一步:clash tun doctor" echo return 1 fi @@ -5409,7 +5415,7 @@ cmd_tun_on_proxy_off() { else ui_warn "系统代理持久块清理失败,Tun 已按前序结果处理" fi - ui_next "clashctl doctor" + ui_next "clash doctor" ui_blank return "$system_proxy_rc" } @@ -5433,7 +5439,7 @@ cmd_tun_off_proxy_on() { else ui_warn "系统代理持久接管写入失败,Tun 已按前序结果处理" fi - ui_next "clashctl doctor" + ui_next "clash doctor" ui_blank return "$system_proxy_rc" } @@ -5594,7 +5600,7 @@ doctor_tun_checks() { if [ "${runtime_tun_auto_redirect:-false}" != "true" ] \ && [ "$env_type" = "host" ] \ && [ "$(runtime_kernel_type 2>/dev/null || echo mihomo)" = "mihomo" ]; then - echo " 💡 auto-redirect 建议:裸 TCP 透明接管超时时,设置 CLASH_TUN_AUTO_REDIRECT=true 后重新执行 clashctl tun on" + echo " 💡 auto-redirect 建议:裸 TCP 透明接管超时时,设置 CLASH_TUN_AUTO_REDIRECT=true 后重新执行 clash tun on" fi else echo " 🚨 运行时配置:不存在" @@ -6274,7 +6280,7 @@ tun_doctor_action_lines() { ;; script) echo "👉 当前运行后端为 script;请确认启动 mihomo 的实际进程具备 CAP_NET_ADMIN / CAP_NET_RAW" - echo "👉 若当前就是主机脚本模式,可尝试提升权限后重新执行:sudo clashctl tun on" + echo "👉 若当前就是主机脚本模式,可尝试提升权限后重新执行:sudo clash tun on" ;; *) echo "👉 请结合运行后端、容器环境和进程能力证据,确认 mihomo 实际拥有 CAP_NET_ADMIN / CAP_NET_RAW" @@ -6311,13 +6317,13 @@ tun_doctor_action_lines() { echo "👉 请先启动代理:clashon" ;; controller-unreachable) - echo "👉 请先修复控制器可访问性:clashctl doctor" + echo "👉 请先修复控制器可访问性:clash doctor" ;; disabled-in-state|disabled-in-runtime-config) - echo "👉 请重新同步 Tun 配置:clashctl tun off && clashctl tun on" + echo "👉 请重新同步 Tun 配置:clash tun off && clash tun on" ;; tun-disabled) - echo "👉 请开启 Tun:clashctl tun on" + echo "👉 请开启 Tun:clash tun on" ;; host-ip-unavailable|current-ip-unavailable) echo "👉 请先确认服务器可访问 https://ip.sb:curl https://ip.sb" @@ -6326,7 +6332,7 @@ tun_doctor_action_lines() { if tun_log_has_tun_traffic_evidence 2>/dev/null; then echo "👉 已看到 Tun 流量证据,但公网出口验证仍未通过;请结合 ip rule、ip route show table all 和内核日志判断是否为策略路由/部分生效" else - echo "👉 当前流量仍走本机出口,请检查 Tun 路由规则和内核日志:clashctl logs" + echo "👉 当前流量仍走本机出口,请检查 Tun 路由规则和内核日志:clash logs" fi ;; default-route-not-tun|main-route-unchanged-needs-policy-check) @@ -6334,13 +6340,13 @@ tun_doctor_action_lines() { ;; policy-routing-likely-effective) if tun_log_tun_source_line >/dev/null 2>&1; then - echo "👉 已看到 Tun policy routing 与 Tun 源地址流量;如仍有访问异常,请重点检查分流规则和内核日志:clashctl logs" + echo "👉 已看到 Tun policy routing 与 Tun 源地址流量;如仍有访问异常,请重点检查分流规则和内核日志:clash logs" else - echo "👉 Tun policy routing 已安装;主动流量验证不足,建议发起一次直连外网访问后重新执行:clashctl tun doctor" + echo "👉 Tun policy routing 已安装;主动流量验证不足,建议发起一次直连外网访问后重新执行:clash tun doctor" fi ;; *) - echo "👉 请查看运行日志定位流量验证失败原因:clashctl logs" + echo "👉 请查看运行日志定位流量验证失败原因:clash logs" ;; esac } @@ -6383,8 +6389,8 @@ tun_recommendation_lines() { if [ "$kernel_support" = "limited" ] && [ "$enabled" != "true" ]; then echo "1. 当前内核为 clash,Tun 仅按降级支持处理" - echo "2. 如需最稳妥 Tun 体验,建议先执行:clashctl config kernel mihomo" - echo "3. 再开启 Tun:clashctl tun on" + echo "2. 如需最稳妥 Tun 体验,建议先执行:clash config kernel mihomo" + echo "3. 再开启 Tun:clash tun on" return 0 fi @@ -6399,9 +6405,9 @@ tun_recommendation_lines() { fi echo "1. Tun 已生效,可继续使用" - echo "2. 如需恢复普通代理模式,执行:clashctl tun off" + echo "2. 如需恢复普通代理模式,执行:clash tun off" if [ "$kernel_support" = "limited" ]; then - echo "3. 当前 Tun 运行在 clash 内核上,如需更稳妥体验可切换:clashctl config kernel mihomo" + echo "3. 当前 Tun 运行在 clash 内核上,如需更稳妥体验可切换:clash config kernel mihomo" fi return 0 @@ -6411,7 +6417,7 @@ tun_recommendation_lines() { [ -n "${disable_result:-}" ] || disable_result="unknown" if [ "$disable_result" != "ok" ]; then - echo "1. Tun 关闭后仍有残留,建议执行:clashctl tun off" + echo "1. Tun 关闭后仍有残留,建议执行:clash tun off" echo "2. 如仍异常,执行:clashoff && clashon" return 0 fi @@ -6421,12 +6427,12 @@ tun_recommendation_lines() { container-risky) echo "👉 当前容器环境已被裁决为高风险:${risk_reason:-容器条件不足}" tun_container_runtime_hint_lines || true - echo "👉 条件满足后再执行:clashctl tun on" + echo "👉 条件满足后再执行:clash tun on" ;; *) echo "1. 当前环境不满足 Tun 基础条件" echo "2. 优先检查:/dev/net/tun、CAP_NET_ADMIN、ip 命令" - echo "3. 条件满足后再执行:clashctl tun on" + echo "3. 条件满足后再执行:clash tun on" ;; esac return 0 @@ -6434,28 +6440,28 @@ tun_recommendation_lines() { if [ "${config_tun_enabled:-false}" = "true" ] && [ "$enabled" != "true" ]; then echo "1. 当前运行配置仍保留 Tun 开启状态" - echo "2. 建议先执行:clashctl tun off" - echo "3. 再观察:clashctl tun doctor" + echo "2. 建议先执行:clash tun off" + echo "3. 再观察:clash tun doctor" return 0 fi case "$container_mode" in container-safe) - echo "1. 当前容器环境已通过保守裁决,可尝试开启 Tun:clashctl tun on" - echo "2. 开启后立即执行:clashctl tun doctor" + echo "1. 当前容器环境已通过保守裁决,可尝试开启 Tun:clash tun on" + echo "2. 开启后立即执行:clash tun doctor" echo "3. 若未生效,优先检查宿主机权限与设备映射" return 0 ;; container-risky) echo "👉 当前容器环境属于高风险,不建议直接开启 Tun:${risk_reason:-容器条件不足}" tun_container_runtime_hint_lines || true - echo "👉 修复后再执行:clashctl tun on" + echo "👉 修复后再执行:clash tun on" return 0 ;; esac - echo "1. 当前环境可尝试开启 Tun:clashctl tun on" - echo "2. 开启后建议立即执行:clashctl tun doctor" + echo "1. 当前环境可尝试开启 Tun:clash tun on" + echo "2. 开启后建议立即执行:clash tun doctor" } tun_problem_lines() { @@ -6587,7 +6593,7 @@ sync_tun_target_state() { ui_error "Tun 配置同步失败,已回滚状态文件" ui_error "$error_summary" - ui_next "clashctl tun doctor" + ui_next "clash tun doctor" return "$rc" } @@ -6617,25 +6623,25 @@ cmd_tun() { *) ui_title "🧪 Tun 模式管理" echo "📜 用法:" - echo " clashctl tun" - echo " clashctl tun status" - echo " clashctl tun on" - echo " clashctl tun off" - echo " clashctl tun on-proxy-off" - echo " clashctl tun off-proxy-on" - echo " clashctl tun doctor" - echo " clashctl tun logs" + echo " clash tun" + echo " clash tun status" + echo " clash tun on" + echo " clash tun off" + echo " clash tun on-proxy-off" + echo " clash tun off-proxy-on" + echo " clash tun doctor" + echo " clash tun logs" echo echo "🧩 说明:" echo " Tun 属于高级接管能力" echo " 开启前应确认环境支持 /dev/net/tun、权限与网络能力" echo echo "💡 常用动作:" - echo " clashctl tun status" - echo " clashctl tun doctor" + echo " clash tun status" + echo " clash tun doctor" echo " clashtun on" echo - ui_next "clashctl tun doctor" + ui_next "clash tun doctor" ui_blank ;; esac @@ -6729,7 +6735,7 @@ cmd_add() { ensure_add_use_prerequisites if [ "${1:-}" = "local" ]; then - [ "$#" -eq 1 ] || die_usage "add local 参数不合法" "clashctl add local" + [ "$#" -eq 1 ] || die_usage "add local 参数不合法" "clash add local" sub_url="$(add_prompt_local_url)" cmd_add "$sub_url" return $? @@ -6787,13 +6793,13 @@ cmd_use() { if [ -n "${active:-}" ]; then ui_title "🐱 当前无更优推荐,保持当前" ui_kv "🚩" "当前主订阅" "$active" - ui_next "clashctl select 选择节点" + ui_next "clash select 选择节点" ui_blank return 0 fi ui_title "❗ 当前没有可推荐的订阅" - ui_next "clashctl select 选择节点" + ui_next "clash select 选择节点" ui_blank return 1 fi @@ -6801,7 +6807,7 @@ cmd_use() { if [ "${recommended:-}" = "${active:-}" ]; then ui_title "🐱 当前已是推荐订阅,无需切换" [ -n "${active:-}" ] && ui_kv "📡" "当前主订阅" "$active" - ui_next "clashctl select 选择节点" + ui_next "clash select 选择节点" ui_blank return 0 fi @@ -6936,22 +6942,22 @@ health_recommendation_lines() { case "$SYSTEM_STATE" in ready) - echo "💡 clashctl status" - echo "💡 clashctl select" + echo "💡 clash status" + echo "💡 clash select" ;; stopped) if [ "$SUBSCRIPTION_STATE" = "missing" ]; then - echo "💡 clashctl add <订阅链接>" + echo "💡 clash add <订阅链接>" else echo "💡 clashon" fi ;; degraded|broken) - echo "💡 clashctl doctor" - echo "💡 clashctl status --verbose" + echo "💡 clash doctor" + echo "💡 clash status --verbose" ;; *) - echo "💡 clashctl status" + echo "💡 clash status" ;; esac } @@ -6962,7 +6968,7 @@ cmd_ls() { case "${1:-}" in "") ;; *) - die_usage "ls 参数不合法" "clashctl ls" + die_usage "ls 参数不合法" "clash ls" ;; esac @@ -6994,7 +7000,7 @@ cmd_health() { if [ -z "${target_name:-}" ]; then target_name="$1" else - die_usage "health 参数不合法" "clashctl health [名称] [--verbose]" + die_usage "health 参数不合法" "clash health [名称] [--verbose]" fi ;; esac @@ -7008,13 +7014,13 @@ cmd_health() { ui_blank ui_section "建议操作" - echo " 💡 clashctl use ${target_name}" - echo " 💡 clashctl ls" + echo " 💡 clash use ${target_name}" + echo " 💡 clash ls" ui_blank return 0 fi - ui_info "订阅健康已收敛到 clashctl ls" + ui_info "订阅健康已收敛到 clash ls" cmd_ls return $? fi @@ -7024,13 +7030,13 @@ cmd_health() { print_subscription_health_one "$target_name" | sed 's/^/ /' ui_blank ui_section "建议操作" - echo " 💡 clashctl use ${target_name}" - echo " 💡 clashctl ls" + echo " 💡 clash use ${target_name}" + echo " 💡 clash ls" ui_blank return 0 fi - ui_info "订阅健康已收敛到 clashctl ls" + ui_info "订阅健康已收敛到 clash ls" cmd_ls } @@ -7062,7 +7068,7 @@ cmd_proxy_groups() { fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi ui_title "📦 策略组列表" @@ -7081,7 +7087,7 @@ cmd_proxy_groups() { fi ui_blank - ui_next "clashctl select" + ui_next "clash select" ui_blank } @@ -7095,7 +7101,7 @@ cmd_proxy_current() { fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi if [ -n "${1:-}" ]; then @@ -7130,14 +7136,14 @@ cmd_proxy_nodes() { local current node found="false" prepare - [ -n "${group:-}" ] || die "请使用 clashctl select 切换节点" + [ -n "${group:-}" ] || die "请使用 clash select 切换节点" if ! status_is_running; then die_state "代理内核未运行" "clashon" fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi current="$(proxy_group_current_display "$group" 2>/dev/null || true)" @@ -7162,7 +7168,7 @@ cmd_proxy_nodes() { fi ui_blank - ui_next "clashctl select" + ui_next "clash select" ui_blank } @@ -7267,19 +7273,19 @@ cmd_sub() { ;; use) shift || true - [ -n "${1:-}" ] || die "用法:clashctl sub use <名称>" + [ -n "${1:-}" ] || die "用法:clash sub use <名称>" set_active_subscription "$1" apply_runtime_change_after_config_mutation ;; set) shift || true - [ -n "${1:-}" ] || die "用法:clashctl sub set [name](固定 convert,推荐使用:clashctl add <订阅链接>)" + [ -n "${1:-}" ] || die "用法:clash sub set [name](固定 convert,推荐使用:clash add <订阅链接>)" set_subscription "${1:-}" "convert" "${2:-default}" apply_runtime_change_after_config_mutation ;; enable) shift || true - [ -n "${1:-}" ] || die "📜 用法:clashctl sub enable <名称>" + [ -n "${1:-}" ] || die "📜 用法:clash sub enable <名称>" enable_subscription "$1" regenerate_config apply_runtime_change_after_config_mutation @@ -7287,7 +7293,7 @@ cmd_sub() { ;; disable) shift || true - [ -n "${1:-}" ] || die "📜 用法:clashctl sub disable <名称>" + [ -n "${1:-}" ] || die "📜 用法:clash sub disable <名称>" disable_subscription "$1" regenerate_config apply_runtime_change_after_config_mutation @@ -7295,8 +7301,8 @@ cmd_sub() { ;; rename) shift || true - [ -n "${1:-}" ] || die "📜 用法:clashctl sub rename <旧名称> <新名称>" - [ -n "${2:-}" ] || die "📜 用法:clashctl sub rename <旧名称> <新名称>" + [ -n "${1:-}" ] || die "📜 用法:clash sub rename <旧名称> <新名称>" + [ -n "${2:-}" ] || die "📜 用法:clash sub rename <旧名称> <新名称>" rename_subscription "$1" "$2" regenerate_config apply_runtime_change_after_config_mutation @@ -7304,7 +7310,7 @@ cmd_sub() { ;; remove|rm|del) shift || true - [ -n "${1:-}" ] || die "📜 用法:clashctl sub remove <名称>" + [ -n "${1:-}" ] || die "📜 用法:clash sub remove <名称>" remove_subscription "$1" regenerate_config apply_runtime_change_after_config_mutation @@ -7317,14 +7323,14 @@ cmd_sub() { "") ui_title "📡 订阅管理" echo "📜 用法:" - echo " clashctl sub update" - echo " clashctl sub list" - echo " clashctl sub use <名称>" - echo " clashctl sub enable <名称>" - echo " clashctl sub disable <名称>" - echo " clashctl sub rename <旧名称> <新名称>" - echo " clashctl sub remove <名称>" - echo " clashctl sub health [名称]" + echo " clash sub update" + echo " clash sub list" + echo " clash sub use <名称>" + echo " clash sub enable <名称>" + echo " clash sub disable <名称>" + echo " clash sub rename <旧名称> <新名称>" + echo " clash sub remove <名称>" + echo " clash sub health [名称]" echo echo "🧩 说明:" echo " add / use / ls / sub update 属于主路径" @@ -7332,16 +7338,16 @@ cmd_sub() { echo " 其他 sub 子命令用于高级维护操作" echo echo "💡 常用动作:" - echo " clashctl sub update" - echo " clashctl sub list" - echo " clashctl sub health" - echo " clashctl sub enable <名称>" + echo " clash sub update" + echo " clash sub list" + echo " clash sub health" + echo " clash sub enable <名称>" echo - ui_next "clashctl ls" + ui_next "clash ls" ui_blank ;; *) - die_usage "未知的 sub 子命令:$1" "clashctl sub" + die_usage "未知的 sub 子命令:$1" "clash sub" ;; esac } @@ -7358,7 +7364,7 @@ proxy_select_interactive() { fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi if [ -z "${group:-}" ]; then @@ -7486,7 +7492,7 @@ proxy_select_interactive_guarded() { fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi if [ -z "${group:-}" ]; then @@ -7544,7 +7550,7 @@ proxy_select_interactive_guarded() { cmd_proxy() { echo "⚠ 当前版本不提供 proxy 子命令" echo "👉 使用 clashon / clashoff 控制代理" - echo "👉 使用 clashctl select 切换节点" + echo "👉 使用 clash select 切换节点" return 0 } @@ -7589,7 +7595,7 @@ cmd_upgrade() { verbose="true" ;; *) - die_usage "upgrade 参数不合法" "clashctl upgrade [mihomo|clash] [-v|--verbose]" + die_usage "upgrade 参数不合法" "clash upgrade [mihomo|clash] [-v|--verbose]" ;; esac shift @@ -7616,7 +7622,7 @@ cmd_upgrade() { ui_kv "🎯" "目标版本" "$target_version" ui_kv "🧪" "实际版本" "$actual_version" ui_kv "🧩" "影响范围" "仅更新代理内核,不更新项目脚本" - ui_next "clashctl status" + ui_next "clash status" ui_blank } @@ -7635,7 +7641,7 @@ cmd_update() { regenerate_mode="true" ;; *) - die_usage "update 参数不合法" "clashctl update [--force] [--regenerate]" + die_usage "update 参数不合法" "clash update [--force] [--regenerate]" ;; esac shift @@ -7654,7 +7660,7 @@ cmd_update() { else ui_kv "🚨" "配置状态" "未自动重新生成" fi - ui_next "clashctl status" + ui_next "clash status" ui_blank } @@ -7755,7 +7761,7 @@ print_select_feedback() { ui_kv "🚀" "当前节点" "${current:-}" main_feedback_runtime_state - ui_next "clashctl status" + ui_next "clash status" ui_blank } @@ -7806,7 +7812,7 @@ cmd_proxy_groups() { fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi ui_title "📦 策略组列表" @@ -7824,7 +7830,7 @@ cmd_proxy_groups() { fi ui_blank - ui_next "clashctl select" + ui_next "clash select" ui_blank } @@ -7838,7 +7844,7 @@ cmd_proxy_current() { fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi if [ -n "${1:-}" ]; then @@ -7871,14 +7877,14 @@ cmd_proxy_nodes() { local current node found="false" prepare - [ -n "${group:-}" ] || die "请使用 clashctl select 切换节点" + [ -n "${group:-}" ] || die "请使用 clash select 切换节点" if ! status_is_running; then die_state "代理内核未运行" "clashon" fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi current="$(proxy_group_current_display "$group" 2>/dev/null || true)" @@ -7903,7 +7909,7 @@ cmd_proxy_nodes() { fi ui_blank - ui_next "clashctl select" + ui_next "clash select" ui_blank } @@ -7966,7 +7972,7 @@ proxy_select_interactive_guarded() { fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi if [ -z "${group:-}" ]; then @@ -8040,7 +8046,7 @@ proxy_select_direct() { local node="${2:-}" if [ "$#" -ne 2 ]; then - die_usage "select 参数不合法" "clashctl select <策略组> <节点>" + die_usage "select 参数不合法" "clash select <策略组> <节点>" fi prepare @@ -8050,7 +8056,7 @@ proxy_select_direct() { fi if ! proxy_controller_reachable 2>/dev/null; then - die_state "控制器不可访问" "clashctl doctor" + die_state "控制器不可访问" "clash doctor" fi proxy_group_select "$group" "$node" diff --git a/scripts/core/common.sh b/scripts/core/common.sh index 4300498..55543a7 100644 --- a/scripts/core/common.sh +++ b/scripts/core/common.sh @@ -243,23 +243,23 @@ install_build_result_text() { install_next_step_text() { case "$(install_state_text)" in ready) - echo "clashctl select" + echo "clash select" ;; verifying) if install_has_subscription; then echo "clashon" else - echo "clashctl add <订阅链接>" + echo "clash add <订阅链接>" fi ;; broken) - echo "clashctl doctor" + echo "clash doctor" ;; stopped) - echo "clashctl add <订阅链接>" + echo "clash add <订阅链接>" ;; *) - echo "clashctl status" + echo "clash status" ;; esac } @@ -951,7 +951,7 @@ download_file() { rm -f "$fetch_tmp" 2>/dev/null || true die_state "下载失败:${asset_name}" \ - "请检查网络连通性,或在 .env 中配置下载源后重试;也可先执行 clashctl doctor" + "请检查网络连通性,或在 .env 中配置下载源后重试;也可先执行 clash doctor" fi ordered_entries="$( @@ -1012,7 +1012,7 @@ EOF rm -f "$fetch_tmp" 2>/dev/null || true die_state "下载失败:${asset_name}" \ - "请检查网络连通性,或在 .env 中配置下载源后重试;也可先执行 clashctl doctor" + "请检查网络连通性,或在 .env 中配置下载源后重试;也可先执行 clash doctor" } download_text_tmp_file() { @@ -2103,7 +2103,7 @@ guard_sudo_on_user_install() { echo "❗ 操作被拒绝:user 安装模式不支持以 sudo 运行" echo "🚨 原因:sudo 会将 runtime 文件写成 root:root,导致 systemctl --user 无法访问" echo "👤 请以安装用户(${SUDO_USER})直接执行:" - echo " clashctl tun ${_action}" + echo " clash tun ${_action}" echo return 1 } @@ -2535,6 +2535,51 @@ clashctl_bin_entry_target() { echo "$(command_install_dir)/clashctl-bin" } +clash_entry_target() { + echo "$(command_install_dir)/clash" +} + +clash_entry_marker() { + echo "# clash-for-linux managed command: clash" +} + +clash_entry_is_managed() { + local target="${1:-}" + + [ -n "${target:-}" ] || target="$(clash_entry_target)" + [ -f "$target" ] || return 1 + grep -Fqx "$(clash_entry_marker)" "$target" 2>/dev/null +} + +install_clash_entry() { + local target bin_target existing_command="" + + target="$(clash_entry_target)" + bin_target="$(clashctl_bin_entry_target)" + + if [ -e "$target" ] || [ -L "$target" ]; then + if ! clash_entry_is_managed "$target"; then + existing_command="$target" + warn "检测到已有 clash 命令,未覆盖:$existing_command" + warn "本项目仍可使用 clashctl;如需 clash 入口,请先自行处理同名命令" + return 0 + fi + elif command -v clash >/dev/null 2>&1; then + existing_command="$(command -v clash 2>/dev/null || true)" + warn "检测到已有 clash 命令,未覆盖:$existing_command" + warn "本项目仍可使用 clashctl;如需 clash 入口,请先自行处理同名命令" + return 0 + fi + + cat > "$target" < "$profile_file" </dev/null || true + fi rm -f "$(clashctl_entry_target)" "$(clashctl_bin_entry_target)" 2>/dev/null || true remove_alias_command_wrappers } @@ -3054,31 +3106,31 @@ install_status_label() { install_default_next_action() { case "$(install_status_text)" in ready) - echo "clashctl select" + echo "clash select" ;; stopped) if install_has_subscription; then echo "clashon" else - echo "clashctl add <订阅链接>" + echo "clash add <订阅链接>" fi ;; verifying) if status_is_running 2>/dev/null; then - echo "clashctl status" + echo "clash status" else echo "clashon" fi ;; broken) if install_has_subscription; then - echo "clashctl doctor" + echo "clash doctor" else - echo "clashctl add <订阅链接>" + echo "clash add <订阅链接>" fi ;; *) - echo "clashctl status" + echo "clash status" ;; esac } @@ -3202,6 +3254,11 @@ print_install_summary() { echo "💻 环境模式:${env_mode_text:-unknown}" echo "📁 安装路径:$project_path" echo "👤 安装方式:${install_actor} / ${install_scope_text:-unknown}" + if clash_entry_is_managed; then + echo "⌨️ 主命令:clash" + else + echo "⚠️ 主命令:clash 同名冲突,未由本项目接管(兼容入口 clashctl 仍可用)" + fi echo "🔧 运行后端:${backend_text:-unknown}" echo "📦 订阅:$subscription_text" [ -n "${node_count:-}" ] && echo "🔢 节点数量:$node_count" @@ -3211,8 +3268,8 @@ print_install_summary() { echo "🚫 安装后验证:代理端口绑定被系统拒绝(非端口冲突)" echo "📌 这通常是环境限制,修改端口通常无法解决" install_mixed_port_bind_observation_line 2>/dev/null || true - echo "👉 下一步:clashctl doctor" - echo "👉 日志:clashctl logs mihomo" + echo "👉 下一步:clash doctor" + echo "👉 日志:clash logs mihomo" ;; address_in_use) : @@ -3225,5 +3282,5 @@ print_install_summary() { echo echo "💡 clashon / clashoff 为 shell 快捷入口;新终端会自动生效" - echo "💡 当前终端若暂不可用,请先使用 clashctl on / clashctl off" + echo "💡 当前终端若暂不可用,请先使用 clash on / clash off" } diff --git a/scripts/core/completion.sh b/scripts/core/completion.sh index 96526bf..c5b4920 100644 --- a/scripts/core/completion.sh +++ b/scripts/core/completion.sh @@ -360,7 +360,7 @@ _clash_for_linux_complete_command() { cur="${COMP_WORDS[COMP_CWORD]:-}" case "$root" in - clashctl) + clash|clashctl) if [ "$COMP_CWORD" -eq 1 ]; then _clash_for_linux_complete_top_level "$cur" return 0 @@ -441,6 +441,9 @@ _clash_for_linux_complete_command() { esac } +if [ "${CLASH_FOR_LINUX_CLASH_ENTRY_MANAGED:-false}" = "true" ]; then + complete -F _clash_for_linux_complete_command clash +fi complete -F _clash_for_linux_complete_command clashctl complete -F _clash_for_linux_complete_command clashtest complete -F _clash_for_linux_complete_command clashsub @@ -477,7 +480,7 @@ cmd_completion() { completion_emit_zsh_script ;; *) - die_usage "completion 参数不合法" "clashctl completion bash|zsh" + die_usage "completion 参数不合法" "clash completion bash|zsh" ;; esac } diff --git a/scripts/core/config.sh b/scripts/core/config.sh index a741184..1793cd7 100644 --- a/scripts/core/config.sh +++ b/scripts/core/config.sh @@ -119,7 +119,7 @@ http_code : $http_code" detail="${detail} process_status : $process_status" detail="${detail} -next_step : clashctl doctor;查看 subconverter_log 定位转换失败原因" +next_step : clash doctor;查看 subconverter_log 定位转换失败原因" SUBCONVERTER_LAST_ERROR_DETAIL="$detail" SUBCONVERTER_LAST_ERROR_SUMMARY="subconverter 转换失败:$failure_type" @@ -1165,8 +1165,8 @@ print_subscription_health_one() { local name="$1" local status fail_count last_success last_failure last_error auto_disabled auto_disabled_at enabled_text type_text url_text - [ -n "${name:-}" ] || die_usage "订阅名称不能为空" "clashctl health <订阅名称>" - subscription_exists "$name" || die_missing "订阅 $name" "clashctl ls" + [ -n "${name:-}" ] || die_usage "订阅名称不能为空" "clash health <订阅名称>" + subscription_exists "$name" || die_missing "订阅 $name" "clash ls" status="$(read_subscription_health_value "$name" "STATUS" 2>/dev/null || echo "unknown")" fail_count="$(read_subscription_health_value "$name" "FAIL_COUNT" 2>/dev/null || echo "0")" @@ -1310,7 +1310,7 @@ set_subscription_enabled() { file="$(subscriptions_file)" ensure_subscriptions_file - subscription_exists "$name" || die_missing "订阅 $name" "clashctl ls" + subscription_exists "$name" || die_missing "订阅 $name" "clash ls" case "$enabled" in true|false) ;; @@ -1417,7 +1417,7 @@ print_build_explain() { esac echo - ui_next "clashctl status --verbose" + ui_next "clash status --verbose" ui_blank } @@ -1519,16 +1519,16 @@ build_explain_next_steps() { failed_csv="$(build_failed_active_sources_csv)" if [ "${last_status:-}" = "success" ]; then - echo "👉 clashctl health" - echo "👉 clashctl select" + echo "👉 clash health" + echo "👉 clash select" return 0 fi if [ -n "${failed_csv:-}" ]; then - echo "👉 clashctl health" + echo "👉 clash health" fi - echo "👉 clashctl doctor" + echo "👉 clash doctor" } runtime_config_file() { @@ -2484,7 +2484,7 @@ set_active_subscription() { exists="$("$(yq_bin)" eval ".sources | has(\"$name\")" "$file")" - [ "$exists" = "true" ] || die_missing "订阅 $name" "clashctl ls" + [ "$exists" = "true" ] || die_missing "订阅 $name" "clash ls" NAME="$name" "$(yq_bin)" eval -i ' .active = env(NAME) @@ -2500,8 +2500,8 @@ remove_subscription() { file="$(subscriptions_file)" ensure_subscriptions_file - [ -n "${name:-}" ] || die_usage "订阅名称不能为空" "clashctl health <订阅名称>" - subscription_exists "$name" || die_missing "订阅 $name" "clashctl ls" + [ -n "${name:-}" ] || die_usage "订阅名称不能为空" "clash health <订阅名称>" + subscription_exists "$name" || die_missing "订阅 $name" "clash ls" if [ "$name" = "default" ]; then die "默认订阅不允许删除:default" @@ -2541,8 +2541,8 @@ rename_subscription() { [ -n "${old_name:-}" ] || die "原订阅名称不能为空" [ -n "${new_name:-}" ] || die "新订阅名称不能为空" - subscription_exists "$old_name" || die_missing "订阅 $old_name" "clashctl ls" - subscription_exists "$new_name" && die_state "目标订阅已存在:$new_name" "clashctl ls" + subscription_exists "$old_name" || die_missing "订阅 $old_name" "clash ls" + subscription_exists "$new_name" && die_state "目标订阅已存在:$new_name" "clash ls" case "$new_name" in *[!A-Za-z0-9._-]*) @@ -3039,7 +3039,7 @@ subscription_list_overview_lines() { } subscription_list_recommendation_lines() { - echo "👉 clashctl use 切换当前使用的订阅" + echo "👉 clash use 切换当前使用的订阅" } detect_subscription_format() { @@ -3076,7 +3076,7 @@ prompt_subscription_if_needed() { if ! subscription_url_is_supported "$input_url"; then ui_warn "订阅链接格式不合法,已跳过" - ui_next "稍后执行:clashctl add <订阅链接>" + ui_next "稍后执行:clash add <订阅链接>" return 0 fi @@ -3221,7 +3221,7 @@ start_subconverter() { fi error "subconverter 启动失败" warn "日志:$log_file" - warn "下一步:请运行 clashctl doctor,或查看上述日志定位 subconverter 异常" + warn "下一步:请运行 clash doctor,或查看上述日志定位 subconverter 异常" return 1 } @@ -3618,7 +3618,7 @@ convert_subscription_via_subconverter() { "${http_code:-unknown}" \ "$process_status" warn "日志:$log_file" - warn "下一步:请运行 clashctl doctor,或查看上述日志定位 subconverter 异常" + warn "下一步:请运行 clash doctor,或查看上述日志定位 subconverter 异常" rm -f "$tmp_file" "$curl_error_file" 2>/dev/null || true return 1 fi @@ -3638,7 +3638,7 @@ convert_subscription_via_subconverter() { "$process_status" warn "订阅转换失败:subconverter 返回内容为空" warn "日志:$log_file" - warn "下一步:请运行 clashctl doctor,或查看上述日志定位 subconverter 异常" + warn "下一步:请运行 clash doctor,或查看上述日志定位 subconverter 异常" rm -f "$tmp_file" 2>/dev/null || true return 1 } @@ -3675,7 +3675,7 @@ convert_subscription_via_subconverter() { warn "订阅转换失败:subconverter 返回内容不是合法 Clash YAML" fi warn "日志:$log_file" - warn "下一步:请运行 clashctl doctor,或查看上述日志定位 subconverter 异常" + warn "下一步:请运行 clash doctor,或查看上述日志定位 subconverter 异常" rm -f "$tmp_file" 2>/dev/null || true return 1 diff --git a/scripts/core/update.sh b/scripts/core/update.sh index fa99b4d..d0526a0 100644 --- a/scripts/core/update.sh +++ b/scripts/core/update.sh @@ -204,7 +204,7 @@ update_project_code() { fi if git_has_local_changes && [ "$force_mode" != "true" ]; then - die "检测到本地有未提交改动,请先提交,或使用 clashctl update --force 强制覆盖" + die "检测到本地有未提交改动,请先提交,或使用 clash update --force 强制覆盖" fi info "正在获取最新代码" diff --git a/scripts/dev/check-clash-command-entry.sh b/scripts/dev/check-clash-command-entry.sh new file mode 100644 index 0000000..8d5fb8f --- /dev/null +++ b/scripts/dev/check-clash-command-entry.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# shellcheck source=../core/common.sh +source "$PROJECT_DIR/scripts/core/common.sh" + +tmp_dir="$(mktemp -d)" +trap 'rm -rf "$tmp_dir"' EXIT + +fixture_bin="$tmp_dir/bin" +mkdir -p "$fixture_bin" + +command_install_dir() { printf '%s\n' "$fixture_bin"; } +ensure_command_install_dir_in_shell_path() { :; } +remove_alias_command_wrappers() { :; } + +install_clashctl_entry + +for command_name in clash clashctl clashctl-bin; do + if [ ! -x "$fixture_bin/$command_name" ]; then + echo "not ok - install should create $command_name" >&2 + exit 1 + fi +done + +primary_help="$("$fixture_bin/clash" --help)" +compat_help="$("$fixture_bin/clashctl" --help)" + +if ! printf '%s\n' "$primary_help" | grep -Fq "clash "; then + echo "not ok - clash help should use the primary command name" >&2 + exit 1 +fi + +if [ "$compat_help" != "$primary_help" ]; then + echo "not ok - clashctl should remain compatible with clash" >&2 + exit 1 +fi + +completion_output="$( + "$fixture_bin/clash" completion bash +)" + +if ! printf '%s\n' "$completion_output" | grep -Eq '^[[:space:]]*complete -F _clash_for_linux_complete_command clash$'; then + echo "not ok - completion should register the clash command" >&2 + exit 1 +fi + +primary_completion="$( + bash --noprofile --norc -c ' + CLASH_FOR_LINUX_CLASH_ENTRY_MANAGED=true + source "$1" + COMP_WORDS=(clash st) + COMP_CWORD=1 + _clash_for_linux_complete_command + printf "%s\n" "${COMPREPLY[@]}" + ' bash <(printf '%s\n' "$completion_output") +)" + +if ! printf '%s\n' "$primary_completion" | grep -Fxq status; then + echo "not ok - clash should complete top-level commands" >&2 + exit 1 +fi + +printf '%s\n' \ + '#!/usr/bin/env bash' \ + 'printf "%s\n" "$*"' > "$fixture_bin/clashctl-bin" +chmod +x "$fixture_bin/clashctl-bin" + +shell_output="$( + PATH="$fixture_bin:/usr/bin:/bin" \ + CLASH_FOR_LINUX_CLASH_ENTRY_MANAGED=true \ + bash --noprofile --norc -c \ + 'source "$1"; clash status' \ + bash "$PROJECT_DIR/scripts/core/alias.sh" +)" + +if [ "$shell_output" != "status" ]; then + echo "not ok - clash shell function should delegate to the shared command path" >&2 + printf 'actual: %s\n' "$shell_output" >&2 + exit 1 +fi + +remove_clashctl_entry + +for command_name in clash clashctl clashctl-bin; do + if [ -e "$fixture_bin/$command_name" ]; then + echo "not ok - uninstall should remove managed $command_name" >&2 + exit 1 + fi +done + +printf '%s\n' '#!/usr/bin/env bash' 'echo foreign-clash' > "$fixture_bin/clash" +chmod +x "$fixture_bin/clash" + +install_clashctl_entry >/dev/null 2>&1 + +if [ "$("$fixture_bin/clash")" != "foreign-clash" ]; then + echo "not ok - install should not overwrite an existing foreign clash command" >&2 + exit 1 +fi + +remove_clashctl_entry + +if [ "$("$fixture_bin/clash")" != "foreign-clash" ]; then + echo "not ok - uninstall should preserve an existing foreign clash command" >&2 + exit 1 +fi + +other_bin="$tmp_dir/other-bin" +foreign_bin="$tmp_dir/foreign-bin" +mkdir -p "$other_bin" "$foreign_bin" +printf '%s\n' '#!/usr/bin/env bash' 'echo foreign-path-clash' > "$foreign_bin/clash" +chmod +x "$foreign_bin/clash" + +command_install_dir() { printf '%s\n' "$other_bin"; } +PATH="$foreign_bin:/usr/bin:/bin" install_clashctl_entry >/dev/null 2>&1 + +if [ -e "$other_bin/clash" ]; then + echo "not ok - install should not shadow a foreign clash command elsewhere in PATH" >&2 + exit 1 +fi + +remove_clashctl_entry + +echo "ok - clash is primary, clashctl stays compatible, and foreign commands are preserved" diff --git a/scripts/dev/check-select-direct.sh b/scripts/dev/check-select-direct.sh index e81b925..eba7d52 100644 --- a/scripts/dev/check-select-direct.sh +++ b/scripts/dev/check-select-direct.sh @@ -41,7 +41,7 @@ run_help_case() { return 1 fi - if ! printf '%s\n' "$output" | grep -Fq "clashctl select <策略组> <节点>"; then + if ! printf '%s\n' "$output" | grep -Fq "clash select <策略组> <节点>"; then echo "not ok - select help missing direct usage" >&2 printf '%s\n' "$output" >&2 return 1 diff --git a/scripts/init/script.sh b/scripts/init/script.sh index caa981e..6e9677b 100644 --- a/scripts/init/script.sh +++ b/scripts/init/script.sh @@ -302,8 +302,9 @@ wait_install_runtime_ready() { write_runtime_value "INSTALL_VERIFY_CONTROLLER_READY" "$controller_ready" } -clashctl_command_available() { - [ -x "$(clashctl_entry_target)" ] \ +clash_command_available() { + clash_entry_is_managed \ + || [ -x "$(clashctl_entry_target)" ] \ || [ -x "$(clashctl_bin_entry_target)" ] \ || command -v clashctl >/dev/null 2>&1 } @@ -314,12 +315,12 @@ post_install_verify() { local controller_ready="false" local bind_failure_kind="" bind_failure_port="" - if clashctl_command_available; then + if clash_command_available; then write_runtime_value "INSTALL_VERIFY_COMMAND_READY" "true" else write_runtime_value "INSTALL_VERIFY_COMMAND_READY" "false" write_runtime_event_value "RUNTIME_LAST_INSTALL_READY" "false" - die "clashctl 安装失败,命令不可用" + die "clash 管理命令安装失败,命令不可用" fi if [ -n "$(subscription_url 2>/dev/null || true)" ]; then @@ -357,8 +358,8 @@ post_install_verify() { echo "🚫 安装后验证发现:代理端口 ${bind_failure_port:-unknown} 绑定被系统拒绝(非端口冲突)" echo "📌 修改端口通常无法解决;install 阶段不会通过自动换端口处理该问题" install_mixed_port_bind_observation_line 2>/dev/null || true - echo "👉 下一步:clashctl doctor" - echo "👉 日志:clashctl logs mihomo" + echo "👉 下一步:clash doctor" + echo "👉 日志:clash logs mihomo" echo fi