Files
cli/tools/test-clis.sh

229 lines
6.6 KiB
Bash
Executable File
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.
#!/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