Files
2026-07-12 21:26:08 +08:00

425 lines
12 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 外挂状态检测脚本:统一判断可选外挂的安装/环境/运行状态
# 五种状态:not_installed / env_unavailable(仅 uv 依赖的来源) / runtime_failed / unsupported / empty_result
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SOURCE_REGISTRY_SCRIPT="$SCRIPT_DIR/source-registry.sh"
# 微信工具 URL 从共享配置读取,与 install.sh 保持一致
source "$SCRIPT_DIR/shared-config.sh"
source "$SCRIPT_DIR/runtime-context.sh"
SKILL_ROOT_OVERRIDE=""
LAYOUT_MODE_OVERRIDE=""
usage() {
cat <<'EOF'
用法:
bash scripts/adapter-state.sh [--skill-root <path>] [--layout-mode <source_checkout|installed_skill|upgrade_target>] check <source_id>
bash scripts/adapter-state.sh [--skill-root <path>] [--layout-mode <source_checkout|installed_skill|upgrade_target>] summary
bash scripts/adapter-state.sh [--skill-root <path>] [--layout-mode <source_checkout|installed_skill|upgrade_target>] summary-human
bash scripts/adapter-state.sh [--skill-root <path>] [--layout-mode <source_checkout|installed_skill|upgrade_target>] classify-run <source_id> <exit_code> <output_path>
EOF
}
resolve_optional_root() {
resolve_optional_adapter_root "$PROJECT_ROOT" "$SKILL_ROOT_OVERRIDE" "$LAYOUT_MODE_OVERRIDE"
}
dependency_installed() {
local dependency_name="$1"
local dependency_type="$2"
local optional_root
case "$dependency_type" in
bundled)
optional_root="$(resolve_optional_root)"
if [ -d "$optional_root/$dependency_name" ]; then
return 0
fi
return 1
;;
install_time)
command -v "$dependency_name" >/dev/null 2>&1
;;
none)
return 0
;;
*)
return 1
;;
esac
}
has_uv() {
command -v uv >/dev/null 2>&1
}
chrome_debug_ready() {
if command -v lsof >/dev/null 2>&1; then
lsof -i :9222 -sTCP:LISTEN >/dev/null 2>&1
return $?
fi
return 1
}
print_header() {
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"source_id" \
"source_label" \
"state" \
"state_label" \
"detail" \
"recovery_action" \
"install_hint" \
"fallback_hint"
}
state_label() {
case "$1" in
available)
printf '%s\n' "可用"
;;
not_installed)
printf '%s\n' "未安装"
;;
env_unavailable)
printf '%s\n' "环境不满足"
;;
runtime_failed)
printf '%s\n' "运行失败"
;;
unsupported)
printf '%s\n' "不支持自动提取"
;;
empty_result)
printf '%s\n' "结果为空"
;;
*)
printf '%s\n' "$1"
;;
esac
}
default_install_hint() {
local source_id="$1"
local adapter_name="$2"
case "$source_id" in
web_article|x_twitter|zhihu_article)
printf '%s\n' "重新运行当前平台的 llm-wiki 安装命令,并追加 --with-optional-adapters,确认 ${adapter_name} 已准备到技能目录"
;;
wechat_article)
printf '%s\n' "先安装 uv,再执行:uv tool install ${WECHAT_TOOL_URL}"
;;
youtube_video)
printf '%s\n' "重新运行当前平台的 llm-wiki 安装命令,并追加 --with-optional-adapters,确认 ${adapter_name} 已准备到技能目录"
;;
*)
printf '%s\n' "-"
;;
esac
}
optional_hint() {
local source_id="$1"
case "$source_id" in
web_article|x_twitter|zhihu_article)
printf '%s\n' '如需复用已登录的浏览器会话,可执行:open -na "Google Chrome" --args --remote-debugging-port=9222'
;;
wechat_article|youtube_video)
printf '%s\n' "先安装 uvbrew install uv"
;;
*)
printf '%s\n' "-"
;;
esac
}
emit_state_row() {
local source_id="$1"
local source_label="$2"
local state="$3"
local detail="$4"
local recovery_action="$5"
local install_hint="$6"
local fallback_hint="$7"
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$source_id" \
"$source_label" \
"$state" \
"$(state_label "$state")" \
"$detail" \
"$recovery_action" \
"$install_hint" \
"$fallback_hint"
}
resolve_preflight_state() {
local source_id="$1"
local record
local source_label source_category input_mode match_rule raw_dir adapter_name dependency_name dependency_type fallback_hint
local state detail recovery_action install_hint
record="$(bash "$SOURCE_REGISTRY_SCRIPT" get "$source_id")" || {
echo "未知来源:$source_id" >&2
exit 1
}
IFS=$'\t' read -r source_id source_label source_category input_mode match_rule raw_dir adapter_name dependency_name dependency_type fallback_hint <<EOF
$record
EOF
case "$source_category" in
core_builtin)
state="available"
detail="核心主线可直接进入,不依赖外挂"
recovery_action="直接继续主线"
install_hint="-"
;;
manual_only)
state="unsupported"
detail="该来源当前只支持手动进入主线"
recovery_action="直接走手动入口"
install_hint="-"
;;
optional_adapter)
case "$source_id" in
wechat_article)
if ! has_uv; then
state="env_unavailable"
detail="缺少 uv,当前无法准备微信公众号自动提取环境"
recovery_action="先补环境;现在也可以直接走手动入口"
install_hint="$(optional_hint "$source_id")"
elif ! dependency_installed "$dependency_name" "$dependency_type"; then
state="not_installed"
detail="未找到 ${adapter_name}"
recovery_action="先补安装;现在也可以直接走手动入口"
install_hint="$(default_install_hint "$source_id" "$adapter_name")"
else
state="available"
detail="${adapter_name} 已可用"
recovery_action="继续自动提取"
install_hint="-"
fi
;;
web_article|x_twitter|zhihu_article)
if ! dependency_installed "$dependency_name" "$dependency_type"; then
state="not_installed"
detail="未找到 ${adapter_name}"
recovery_action="先补安装;现在也可以直接走手动入口"
install_hint="$(default_install_hint "$source_id" "$adapter_name")"
else
state="available"
if chrome_debug_ready; then
detail="${adapter_name} 已可用,且已检测到可复用的 Chrome 调试会话"
recovery_action="继续自动提取"
install_hint="-"
else
detail="${adapter_name} 已可用;未检测到 9222,将在需要时自动拉起临时浏览器"
recovery_action="继续自动提取;如需复用已登录会话,可先开启 Chrome 调试端口 9222"
install_hint="$(optional_hint "$source_id")"
fi
fi
;;
youtube_video)
if ! dependency_installed "$dependency_name" "$dependency_type"; then
state="not_installed"
detail="未找到 ${adapter_name}"
recovery_action="先补安装;现在也可以直接走手动入口"
install_hint="$(default_install_hint "$source_id" "$adapter_name")"
elif ! has_uv; then
state="env_unavailable"
detail="缺少 uv,当前无法运行 YouTube 字幕提取"
recovery_action="先补环境;现在也可以直接走手动入口"
install_hint="$(optional_hint "$source_id")"
else
state="available"
detail="${adapter_name} 已可用"
recovery_action="继续自动提取"
install_hint="-"
fi
;;
*)
if ! dependency_installed "$dependency_name" "$dependency_type"; then
state="not_installed"
detail="未找到 ${adapter_name}"
recovery_action="先补安装;现在也可以直接走手动入口"
install_hint="$(default_install_hint "$source_id" "$adapter_name")"
else
state="available"
detail="${adapter_name} 已可用"
recovery_action="继续自动提取"
install_hint="-"
fi
;;
esac
;;
*)
echo "未知来源分类:$source_category" >&2
exit 1
;;
esac
emit_state_row \
"$source_id" \
"$source_label" \
"$state" \
"$detail" \
"$recovery_action" \
"$install_hint" \
"$fallback_hint"
}
classify_run_state() {
local source_id="$1"
local exit_code="$2"
local output_path="$3"
# 校验 exit_code 为整数,防止 set -e 下非数字参数导致脚本崩溃
case "$exit_code" in
''|*[!0-9-]*) echo "exit_code 必须是整数,收到:$exit_code" >&2; exit 1 ;;
esac
local row
local source_label state state_label_value detail recovery_action install_hint fallback_hint
row="$(resolve_preflight_state "$source_id")"
IFS=$'\t' read -r _ source_label state state_label_value detail recovery_action install_hint fallback_hint <<EOF
$row
EOF
if [ "$state" != "available" ]; then
emit_state_row \
"$source_id" \
"$source_label" \
"$state" \
"$detail" \
"$recovery_action" \
"$install_hint" \
"$fallback_hint"
return 0
fi
if [ "$exit_code" -ne 0 ]; then
emit_state_row \
"$source_id" \
"$source_label" \
"runtime_failed" \
"自动提取执行失败" \
"可以先重试一次;如果还不行,就改走手动入口" \
"-" \
"$fallback_hint"
return 0
fi
if [ ! -f "$output_path" ] || ! grep -q '[^[:space:]]' "$output_path" 2>/dev/null; then
emit_state_row \
"$source_id" \
"$source_label" \
"empty_result" \
"自动提取完成,但没有拿到有效正文" \
"请手动补全文本后继续主线" \
"-" \
"$fallback_hint"
return 0
fi
emit_state_row \
"$source_id" \
"$source_label" \
"available" \
"自动提取已拿到有效正文" \
"继续进入主线" \
"-" \
"$fallback_hint"
}
print_summary() {
local source_id
print_header
while IFS=$'\t' read -r source_id _; do
[ -n "$source_id" ] || continue
resolve_preflight_state "$source_id"
done <<EOF
$(bash "$SOURCE_REGISTRY_SCRIPT" list | awk -F '\t' 'NR > 1 && ($3 == "optional_adapter" || $3 == "manual_only") { print $1 "\t" $2 }')
EOF
}
print_summary_human() {
local row
local source_id source_label state state_label_value detail recovery_action install_hint fallback_hint
while IFS= read -r row; do
[ -n "$row" ] || continue
IFS=$'\t' read -r source_id source_label state state_label_value detail recovery_action install_hint fallback_hint <<EOF
$row
EOF
printf '%s\n' "- ${source_label}${state_label_value}${detail}。"
printf '%s\n' " 下一步:${recovery_action}。"
if [ "$install_hint" != "-" ]; then
if [ "$state" = "available" ]; then
printf '%s\n' " 补充说明:${install_hint}。"
else
printf '%s\n' " 安装提示:${install_hint}。"
fi
fi
printf '%s\n' " 回退方式:${fallback_hint}。"
done <<EOF
$(print_summary | tail -n +2)
EOF
}
while [ $# -gt 0 ]; do
case "$1" in
--skill-root)
[ $# -ge 2 ] || { usage; exit 1; }
SKILL_ROOT_OVERRIDE="$2"
shift 2
;;
--layout-mode)
[ $# -ge 2 ] || { usage; exit 1; }
LAYOUT_MODE_OVERRIDE="$2"
shift 2
;;
*)
break
;;
esac
done
command_name="${1:-}"
case "$command_name" in
check)
[ "$#" -eq 2 ] || { usage; exit 1; }
print_header
resolve_preflight_state "$2"
;;
summary)
[ "$#" -eq 1 ] || { usage; exit 1; }
print_summary
;;
summary-human)
[ "$#" -eq 1 ] || { usage; exit 1; }
print_summary_human
;;
classify-run)
[ "$#" -eq 4 ] || { usage; exit 1; }
print_header
classify_run_state "$2" "$3" "$4"
;;
*)
usage
exit 1
;;
esac