#!/bin/sh set -u SCRIPT_DIR=$(CDPATH=; cd "$(dirname "$0")" && pwd) || exit 1 say() { printf '%s\n' "$*" } warn() { printf '错误: %s\n' "$*" >&2 } usage() { cat >&2 <<'EOF' 用法: ./configure.sh search [选项] 配置搜索 API key(tavily / exa / keenable) ./configure.sh ssh [选项] 配置 SSH 主机凭据 EOF } search_usage() { cat <<'EOF' 用法: ./configure.sh search ./configure.sh search --tavily KEY --exa KEY --keenable KEY ./configure.sh search --migrate [search.env 路径] ./configure.sh search --list 选项: --tavily KEY 配置 tavily api_key 凭据 --exa KEY 配置 exa api_key 凭据 --keenable KEY 配置 keenable api_key 凭据 --migrate 从旧 search.env 导入缺失的 key --list 显示各 provider 配置状态(不输出 key) --auth FILE 覆盖 auth.json 路径 -h, --help 显示帮助 不传 key 参数时进入隐藏输入的交互模式;传入任意 key 参数时不交互, 未指定的 provider 保留已有值。凭据写入 Pi 的 auth.json ($PI_CODING_AGENT_DIR/auth.json 或 ~/.pi/agent/auth.json,权限 600)。 key 支持 auth.json 原生语义:字面量、$ENV_VAR / ${ENV_VAR} 插值、 以及 !command(执行命令取 stdout,如 "!op read op://Vault/Tavily/key")。 EOF } configure_search() { SEARCH_AUTH_MJS="$SCRIPT_DIR/scripts/search-auth.mjs" [ -f "$SEARCH_AUTH_MJS" ] || { warn "找不到 ${SEARCH_AUTH_MJS}"; exit 1; } SEARCH_AUTH_FILE= search_mode=interactive search_did_work=0 run_search_auth() { if [ -n "$SEARCH_AUTH_FILE" ]; then node "$SEARCH_AUTH_MJS" --auth "$SEARCH_AUTH_FILE" "$@" else node "$SEARCH_AUTH_MJS" "$@" fi } search_has_key() { run_search_auth --has "$1" >/dev/null 2>&1 } search_prompt_key() { prompt_label=$1 prompt_configured=$2 if [ ! -t 0 ]; then warn "交互模式需要终端;请改用 --tavily/--exa/--keenable 参数。" return 2 fi if [ "$prompt_configured" -eq 1 ]; then printf '%s API key(回车保留现有值): ' "$prompt_label" >&2 else printf '%s API key(回车跳过): ' "$prompt_label" >&2 fi stty -echo IFS= read -r prompt_value || prompt_status=$? stty echo printf '\n' >&2 [ "${prompt_status:-0}" -eq 0 ] || return "$prompt_status" printf '%s\n' "$prompt_value" } while [ "$#" -gt 0 ]; do case $1 in --tavily|--exa|--keenable) [ "$#" -ge 2 ] || { warn "$1 缺少 KEY"; exit 2; } search_mode=noninteractive; search_did_work=1 run_search_auth --set "${1#--}=$2" || exit $? shift 2 ;; --migrate) search_mode=noninteractive; search_did_work=1 if [ "$#" -ge 2 ] && [ "${2#--}" = "$2" ]; then run_search_auth --migrate "$2" || exit $? shift 2 else run_search_auth --migrate || exit $? shift fi say "如确认 auth.json 可用,可手动删除旧 search.env 文件。" ;; --list) search_mode=noninteractive; search_did_work=1; run_search_auth --list || exit $?; shift ;; --auth) [ "$#" -ge 2 ] || { warn "--auth 缺少 FILE"; exit 2; } SEARCH_AUTH_FILE=$2; shift 2 ;; -h|--help) search_usage; exit 0 ;; *) warn "未知参数:$1"; search_usage >&2; exit 2 ;; esac done if [ "$search_mode" = interactive ]; then for provider in tavily exa keenable; do case $provider in tavily) label=Tavily ;; exa) label=Exa ;; keenable) label=Keenable ;; esac if search_has_key "$provider"; then configured=1; else configured=0; fi value=$(search_prompt_key "$label" "$configured") || exit $? if [ -n "$value" ]; then run_search_auth --set "$provider=$value" || exit $? search_did_work=1 fi done [ "$search_did_work" -eq 1 ] || say "未提供任何 key,auth.json 保持不变。" fi } case ${1:-} in search) shift configure_search "$@" ;; ssh) shift exec node "$SCRIPT_DIR/pi-ssh/scripts/ssh-config-launcher.mjs" "$@" ;; *) usage exit 2 ;; esac