229 lines
6.6 KiB
Bash
Executable File
229 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env sh
|
||
# test-clis.sh — 實際呼叫已安裝的 AI CLI,找出靜態設定看不出的環境問題。
|
||
# 用法:
|
||
# test-clis.sh [cli...]
|
||
# 輸出(TSV):
|
||
# test<TAB>{area}<TAB>{cli}<TAB>{test}<TAB>{指令}<TAB>{結束碼}<TAB>{判定}<TAB>{說明}
|
||
# summary<TAB>{ok}<TAB>{warn}<TAB>{fail}<TAB>{skipped}
|
||
# 判定:
|
||
# ok 實際命令成功
|
||
# warn CLI 可用,但該功能在這支 CLI 或這個版本不是必要功能
|
||
# fail 命令失敗、逾時,或必要功能不存在
|
||
# skipped 未安裝或無法執行該項測試
|
||
# 結束碼:0=測試完成;2=用法錯誤;3=找不到 detect-clis.sh
|
||
set -u
|
||
|
||
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||
DETECT="$HERE/detect-clis.sh"
|
||
TIMEOUT_SECONDS="${JSC_CLI_TEST_TIMEOUT:-10}"
|
||
ok_count=0
|
||
warn_count=0
|
||
fail_count=0
|
||
skipped_count=0
|
||
|
||
[ -f "$DETECT" ] || { echo "找不到 detect-clis.sh:$DETECT" >&2; exit 3; }
|
||
|
||
usage() {
|
||
echo "用法:test-clis.sh [claude|codex|copilot|antigravity|kiro ...]" >&2
|
||
exit 2
|
||
}
|
||
|
||
cli_bin() {
|
||
case "$1" in
|
||
antigravity) printf 'agy' ;;
|
||
kiro) printf 'kiro-cli' ;;
|
||
claude|codex|copilot) printf '%s' "$1" ;;
|
||
*) return 1 ;;
|
||
esac
|
||
}
|
||
|
||
emit() { # area cli test command code verdict detail
|
||
printf 'test\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" "$5" "$6" "$7"
|
||
case "$6" in
|
||
ok) ok_count=$((ok_count + 1)) ;;
|
||
warn) warn_count=$((warn_count + 1)) ;;
|
||
fail) fail_count=$((fail_count + 1)) ;;
|
||
skipped) skipped_count=$((skipped_count + 1)) ;;
|
||
esac
|
||
}
|
||
|
||
run_capture() { # $@=command
|
||
out_file=$(mktemp) || return 125
|
||
err_file=$(mktemp) || { rm -f "$out_file"; return 125; }
|
||
if command -v timeout >/dev/null 2>&1; then
|
||
timeout "$TIMEOUT_SECONDS" "$@" >"$out_file" 2>"$err_file"
|
||
else
|
||
"$@" >"$out_file" 2>"$err_file"
|
||
fi
|
||
code=$?
|
||
text=$(cat "$out_file" "$err_file" 2>/dev/null | tr '\r\n\t' ' ' | sed 's/[[:space:]][[:space:]]*/ /g; s/^ //; s/ $//; s/.*TOKEN[^ ]*/[secret]/g' | cut -c1-180)
|
||
rm -f "$out_file" "$err_file"
|
||
RUN_CODE=$code
|
||
RUN_TEXT=${text:-無輸出}
|
||
return 0
|
||
}
|
||
|
||
run_required() { # area cli test command...
|
||
area=$1
|
||
shift
|
||
cli=$1
|
||
test_name=$2
|
||
shift 2
|
||
cmd_text="$*"
|
||
run_capture "$@"
|
||
code=$RUN_CODE
|
||
detail=$RUN_TEXT
|
||
if [ "$code" -eq 0 ]; then
|
||
emit "$area" "$cli" "$test_name" "$cmd_text" "$code" ok "$detail"
|
||
elif [ "$code" -eq 124 ]; then
|
||
emit "$area" "$cli" "$test_name" "$cmd_text" "$code" fail "命令逾時(${TIMEOUT_SECONDS} 秒)"
|
||
else
|
||
emit "$area" "$cli" "$test_name" "$cmd_text" "$code" fail "$detail"
|
||
fi
|
||
}
|
||
|
||
run_optional() { # area cli test command...
|
||
area=$1
|
||
shift
|
||
cli=$1
|
||
test_name=$2
|
||
shift 2
|
||
cmd_text="$*"
|
||
run_capture "$@"
|
||
code=$RUN_CODE
|
||
detail=$RUN_TEXT
|
||
if [ "$code" -eq 0 ]; then
|
||
emit "$area" "$cli" "$test_name" "$cmd_text" "$code" ok "$detail"
|
||
elif [ "$code" -eq 124 ]; then
|
||
emit "$area" "$cli" "$test_name" "$cmd_text" "$code" fail "命令逾時(${TIMEOUT_SECONDS} 秒)"
|
||
else
|
||
emit "$area" "$cli" "$test_name" "$cmd_text" "$code" warn "$detail"
|
||
fi
|
||
}
|
||
|
||
has_cli() {
|
||
name=$1
|
||
"$DETECT" | awk -F '\t' -v name="$name" '$1 == name { found = 1 } END { exit found ? 0 : 1 }'
|
||
}
|
||
|
||
latest_dir() {
|
||
for pat in "$@"; do
|
||
# shellcheck disable=SC2086
|
||
ls -d $pat 2>/dev/null | sort | tail -n1
|
||
done | tail -n1
|
||
}
|
||
|
||
claude_plugin_root() {
|
||
reg="$HOME/.claude/plugins/installed_plugins.json"
|
||
[ -f "$reg" ] || return 1
|
||
tr -d '\n' < "$reg" \
|
||
| sed -n 's/.*"jsc-cli@jsc"[[:space:]]*:[[:space:]]*\[[^]]*"installPath"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
||
| head -n1
|
||
}
|
||
|
||
skill_root() {
|
||
case "$1" in
|
||
claude) claude_plugin_root ;;
|
||
codex) latest_dir "$HOME/.codex/plugins/cache/jsc/jsc-cli"/* ;;
|
||
copilot) printf '%s\n' "$HOME/.copilot/installed-plugins/jsc/jsc-cli" ;;
|
||
antigravity) latest_dir "$HOME/.antigravity"/*/jsc-cli "$HOME/.config/antigravity"/*/jsc-cli ;;
|
||
kiro) printf '%s\n' "$HOME/.kiro/skills/jsc-cli" ;;
|
||
*) return 1 ;;
|
||
esac
|
||
}
|
||
|
||
skill_file() {
|
||
root=$(skill_root "$1" 2>/dev/null || true)
|
||
[ -n "$root" ] || return 1
|
||
for f in "$root/skills/doctor/SKILL.md" "$root/doctor/SKILL.md"; do
|
||
[ -f "$f" ] && { printf '%s\n' "$f"; return 0; }
|
||
done
|
||
return 1
|
||
}
|
||
|
||
hooks_wire() {
|
||
if [ -n "${JSC_HOOKS_TOOLS:-}" ] && [ -f "$JSC_HOOKS_TOOLS/wire-cli.sh" ]; then
|
||
printf '%s\n' "$JSC_HOOKS_TOOLS/wire-cli.sh"
|
||
return 0
|
||
fi
|
||
for f in "$HERE/../../hooks/tools/wire-cli.sh" "$HOME/.codex/plugins/cache/jsc/jsc-hooks"/*/tools/wire-cli.sh "$HOME/.claude/plugins/cache/jsc/jsc-hooks"/*/tools/wire-cli.sh "$HOME/.copilot/installed-plugins/jsc/jsc-hooks/tools/wire-cli.sh"; do
|
||
[ -f "$f" ] && { printf '%s\n' "$f"; return 0; }
|
||
done
|
||
return 1
|
||
}
|
||
|
||
test_skill() {
|
||
cli=$1
|
||
if f=$(skill_file "$cli" 2>/dev/null); then
|
||
emit skill "$cli" doctor-skill "$f" 0 ok "找到 doctor SKILL.md"
|
||
else
|
||
emit skill "$cli" doctor-skill "jsc-cli doctor skill" "-" fail "找不到已安裝的 doctor SKILL.md"
|
||
fi
|
||
}
|
||
|
||
test_hooks() {
|
||
cli=$1
|
||
if wire=$(hooks_wire 2>/dev/null); then
|
||
run_required hook "$cli" hook-smoke sh "$wire" smoke "$cli"
|
||
else
|
||
emit hook "$cli" hook-smoke "wire-cli.sh smoke $cli" "-" skipped "找不到 wire-cli.sh"
|
||
fi
|
||
}
|
||
|
||
test_one() {
|
||
cli=$1
|
||
bin=$(cli_bin "$cli") || usage
|
||
path=$(command -v "$bin" 2>/dev/null || true)
|
||
if [ -z "$path" ]; then
|
||
emit cli "$cli" executable "$bin" "-" skipped "未偵測到執行檔"
|
||
return 0
|
||
fi
|
||
|
||
case "$cli" in
|
||
antigravity)
|
||
run_required cli "$cli" version "$path" --version
|
||
run_optional cli "$cli" help "$path" --help
|
||
run_required cli "$cli" plugin-list "$path" plugin list
|
||
;;
|
||
kiro)
|
||
run_required cli "$cli" version "$path" --version
|
||
run_optional cli "$cli" help "$path" --help-all
|
||
run_optional cli "$cli" plugin-list "$path" plugin list
|
||
;;
|
||
*)
|
||
run_required cli "$cli" version "$path" --version
|
||
run_optional cli "$cli" help "$path" --help
|
||
run_required cli "$cli" plugin-list "$path" plugin list
|
||
;;
|
||
esac
|
||
test_skill "$cli"
|
||
test_hooks "$cli"
|
||
}
|
||
|
||
if [ "$#" -eq 0 ]; then
|
||
set -- $("$DETECT" | cut -f1)
|
||
fi
|
||
|
||
if [ "$#" -eq 0 ]; then
|
||
emit cli all executable "-" "-" skipped "未偵測到任何支援的 CLI"
|
||
printf 'summary\t%s\t%s\t%s\t%s\n' "$ok_count" "$warn_count" "$fail_count" "$skipped_count"
|
||
exit 0
|
||
fi
|
||
|
||
for cli in "$@"; do
|
||
case "$cli" in
|
||
claude|codex|copilot|antigravity|kiro)
|
||
if has_cli "$cli"; then
|
||
test_one "$cli"
|
||
else
|
||
bin=$(cli_bin "$cli") || usage
|
||
emit cli "$cli" executable "$bin" "-" skipped "未偵測到執行檔"
|
||
fi
|
||
;;
|
||
*) usage ;;
|
||
esac
|
||
done
|
||
|
||
printf 'summary\t%s\t%s\t%s\t%s\n' "$ok_count" "$warn_count" "$fail_count" "$skipped_count"
|
||
exit 0
|