feat(scaffold): align generated install wizard with latest TUI flow

This commit is contained in:
thibaud-lclr 2026-04-15 11:29:45 +02:00
parent 9d862c876f
commit 3eeb2fe173
2 changed files with 185 additions and 63 deletions

View file

@ -339,6 +339,9 @@ BINARY_NAME="{{.BinaryName}}"
MODULE_PATH="{{.ModulePath}}"
DEFAULT_PROFILE="{{.DefaultProfile}}"
PROFILE_ENV="{{.ProfileEnv}}"
PREFILL_SERVER_NAME=""
PREFILL_PROFILE_VALUE=""
PREFILL_COMMAND_PATH=""
if [ -t 2 ] && [ -z "${NO_COLOR:-}" ]; then
C_RESET="$(printf '\033[0m')"
@ -413,6 +416,101 @@ prompt() {
printf "%s" "$answer"
}
tty_prompt_available() {
[ -t 2 ] && [ -r /dev/tty ] && [ -w /dev/tty ]
}
menu_select() {
local title="$1"
shift
local options=("$@")
local count="${#options[@]}"
local index=0
local key=""
local i=0
local rows=$((count + 3))
local rendered=0
if [ "$count" -eq 0 ]; then
return 1
fi
if ! tty_prompt_available; then
ui_title "$title"
i=0
while [ "$i" -lt "$count" ]; do
printf " %d) %s\n" "$((i + 1))" "${options[$i]}" >&2
i=$((i + 1))
done
while true; do
local raw_choice
raw_choice="$(prompt "Choix" "1")"
case "$raw_choice" in
''|*[!0-9]*)
ui_warn "Choix invalide: $raw_choice"
;;
*)
if [ "$raw_choice" -ge 1 ] && [ "$raw_choice" -le "$count" ]; then
printf "%s" "${options[$((raw_choice - 1))]}"
return 0
fi
ui_warn "Choix invalide: $raw_choice"
;;
esac
done
fi
while true; do
if [ "$rendered" -eq 1 ]; then
printf "\033[%dA\033[J" "$rows" >&2 2>/dev/null || true
fi
ui_title "$title"
i=0
while [ "$i" -lt "$count" ]; do
if [ "$i" -eq "$index" ]; then
printf " %b %s%b\n" "$C_BOLD$C_CYAN" "${options[$i]}" "$C_RESET" >&2
else
printf " %s\n" "${options[$i]}" >&2
fi
i=$((i + 1))
done
printf "%bUtilise ↑/↓ puis Entrée.%b\n" "$C_DIM" "$C_RESET" >&2
rendered=1
if ! IFS= read -rsn1 key < /dev/tty; then
continue
fi
case "$key" in
"")
printf "%s" "${options[$index]}"
return 0
;;
$'\x1b')
if IFS= read -rsn2 key < /dev/tty; then
case "$key" in
"[A")
if [ "$index" -eq 0 ]; then
index=$((count - 1))
else
index=$((index - 1))
fi
;;
"[B")
if [ "$index" -eq $((count - 1)) ]; then
index=0
else
index=$((index + 1))
fi
;;
esac
fi
;;
esac
done
}
sanitize_server_name() {
local raw="$1"
local sanitized
@ -505,48 +603,46 @@ run_setup_wizard() {
binary_path="$(resolve_binary_path)"
ui_info "Lancement de $BINARY_NAME setup"
if [ -r /dev/tty ] && [ -w /dev/tty ]; then
if [ -t 2 ] && [ -r /dev/tty ] && [ -w /dev/tty ]; then
env "${PROFILE_ENV}=${profile}" "$binary_path" setup < /dev/tty > /dev/tty
else
env "${PROFILE_ENV}=${profile}" "$binary_path" setup
fi
ui_success "Setup termine pour le profil \"$profile\"."
PREFILL_SERVER_NAME="$(sanitize_server_name "$BINARY_NAME")"
PREFILL_PROFILE_VALUE="$profile"
PREFILL_COMMAND_PATH="$binary_path"
}
collect_server_inputs() {
local default_name
default_name="$(sanitize_server_name "$BINARY_NAME")"
default_name="$(sanitize_server_name "${PREFILL_SERVER_NAME:-$BINARY_NAME}")"
SERVER_NAME="$(prompt "Nom du serveur MCP" "$default_name")"
SERVER_NAME="$(sanitize_server_name "$SERVER_NAME")"
PROFILE_VALUE="$(prompt "Valeur de ${PROFILE_ENV}" "$DEFAULT_PROFILE")"
PROFILE_VALUE="$(prompt "Valeur de ${PROFILE_ENV}" "${PREFILL_PROFILE_VALUE:-$DEFAULT_PROFILE}")"
local default_command
if [ -n "${PREFILL_COMMAND_PATH:-}" ]; then
default_command="$PREFILL_COMMAND_PATH"
else
default_command="$(resolve_binary_path)"
fi
COMMAND_PATH="$(prompt "Chemin du binaire serveur MCP" "$default_command")"
}
choose_scope() {
local selected
while true; do
ui_title "Scope de configuration"
printf " 1) global (user)\n" >&2
printf " 2) project (projet courant)\n" >&2
selected="$(prompt "Choix" "1")"
selected="$(menu_select "Scope de configuration" "global (user)" "project (projet courant)")"
case "$selected" in
1)
"global (user)")
printf "global"
return
;;
2)
printf "project"
return
;;
*)
ui_warn "Choix invalide: $selected"
printf "project"
;;
esac
done
}
apply_claude_mcp() {
@ -567,8 +663,9 @@ apply_claude_mcp() {
claude mcp add \
--transport stdio \
--scope "$claude_scope" \
-e "${PROFILE_ENV}=${PROFILE_VALUE}" \
"$SERVER_NAME" -- "$COMMAND_PATH" mcp
"$SERVER_NAME" \
--env "${PROFILE_ENV}=${PROFILE_VALUE}" \
-- "$COMMAND_PATH" mcp
ui_success "Serveur \"$SERVER_NAME\" configure dans Claude ($claude_scope)."
}
@ -667,48 +764,69 @@ print_header() {
printf "%bMCP Install Wizard%b for %b%s%b\n" "$C_BOLD$C_MAGENTA" "$C_RESET" "$C_BOLD" "$BINARY_NAME" "$C_RESET" >&2
printf "%bFramework module:%b %s\n" "$C_DIM" "$C_RESET" "$MODULE_PATH" >&2
ui_line
printf "Choisis une action:\n" >&2
printf " 1) Installer/mettre a jour le binaire + setup\n" >&2
printf " 2) Configurer Claude Code (apply direct)\n" >&2
printf " 3) Configurer Codex (apply direct)\n" >&2
printf " 4) Generer JSON MCP manuel\n" >&2
printf " 5) Quitter\n" >&2
printf "%bSelectionne une action dans le menu interactif.%b\n" "$C_DIM" "$C_RESET" >&2
}
post_setup_configure_mcp() {
ui_title "Configuration MCP apres setup"
local next_action
next_action="$(menu_select \
"Configurer le MCP maintenant ?" \
"Configurer Claude Code (apply direct)" \
"Configurer Codex (apply direct)" \
"Generer JSON MCP manuel" \
"Terminer sans config MCP")"
printf "\n" >&2
case "$next_action" in
"Configurer Claude Code (apply direct)")
apply_claude_mcp
;;
"Configurer Codex (apply direct)")
apply_codex_mcp
;;
"Generer JSON MCP manuel")
ui_info "JSON MCP genere sur stdout."
print_mcp_json
;;
*)
ui_info "Setup termine sans configuration MCP additionnelle."
;;
esac
}
main() {
while true; do
print_header
local choice
choice="$(prompt "Choix" "1")"
local action
action="$(menu_select \
"Choisis une action" \
"Installer/mettre a jour le binaire + setup" \
"Configurer Claude Code (apply direct)" \
"Configurer Codex (apply direct)" \
"Generer JSON MCP manuel" \
"Quitter")"
printf "\n" >&2
case "$choice" in
1)
case "$action" in
"Installer/mettre a jour le binaire + setup")
run_setup_wizard
return
post_setup_configure_mcp
;;
2)
"Configurer Claude Code (apply direct)")
apply_claude_mcp
return
;;
3)
"Configurer Codex (apply direct)")
apply_codex_mcp
return
;;
4)
"Generer JSON MCP manuel")
ui_info "JSON MCP genere sur stdout."
print_mcp_json
return
;;
5)
ui_warn "Annule."
return
;;
*)
ui_warn "Choix invalide: $choice"
ui_warn "Annule."
;;
esac
done
}
main "$@"

View file

@ -121,9 +121,13 @@ func TestGenerateCreatesRecommendedSkeleton(t *testing.T) {
`MODULE_PATH="example.com/acme/my-mcp"`,
`go install "${MODULE_PATH}/cmd/${BINARY_NAME}@latest"`,
"MCP Install Wizard",
`menu_select() {`,
`Utilise ↑/↓ puis Entrée.`,
`Configurer le MCP maintenant ?`,
`claude mcp add \`,
`--transport stdio \`,
`--scope "$claude_scope" \`,
`--env "${PROFILE_ENV}=${PROFILE_VALUE}" \`,
`codex mcp add \`,
`Dossier projet cible pour .codex/config.toml`,
`[mcp_servers.%s]`,