#!/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 ] [--layout-mode ] check bash scripts/adapter-state.sh [--skill-root ] [--layout-mode ] summary bash scripts/adapter-state.sh [--skill-root ] [--layout-mode ] summary-human bash scripts/adapter-state.sh [--skill-root ] [--layout-mode ] classify-run 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' "先安装 uv:brew 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 <&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 </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 < 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 <