Harden Zacus CLI for mock workflows
This commit is contained in:
Vendored
+17
-19
@@ -5,7 +5,8 @@
|
||||
"label": "Zacus: Firmware · Bootstrap venv",
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": ["-lc", "cd hardware/firmware && ./tools/dev/bootstrap_local.sh"],
|
||||
"args": ["-lc", "./tools/dev/bootstrap_local.sh"],
|
||||
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||
"problemMatcher": [],
|
||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||
},
|
||||
@@ -15,8 +16,9 @@
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-lc",
|
||||
"cd hardware/firmware && ./tools/dev/bootstrap_local.sh && export PLATFORMIO_CORE_DIR=\"$HOME/.platformio\" && ./build_all.sh"
|
||||
"./tools/dev/bootstrap_local.sh && export PLATFORMIO_CORE_DIR=\"$HOME/.platformio\" && ./build_all.sh"
|
||||
],
|
||||
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||
"problemMatcher": [],
|
||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||
},
|
||||
@@ -25,7 +27,7 @@
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": ["-lc", "./tools/dev/zacus.sh rc"],
|
||||
"options": { "cwd": "${workspaceFolder}" },
|
||||
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||
"problemMatcher": [],
|
||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||
},
|
||||
@@ -37,18 +39,7 @@
|
||||
"-lc",
|
||||
"./tools/dev/zacus.sh codex --prompt tools/dev/codex_prompts/zacus_overhaul_one_shot.md"
|
||||
],
|
||||
"options": { "cwd": "${workspaceFolder}" },
|
||||
"problemMatcher": [],
|
||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||
},
|
||||
{
|
||||
"label": "Zacus: Firmware · RC live FAST (--skip-build --skip-upload)",
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-lc",
|
||||
"cd hardware/firmware && ./tools/dev/bootstrap_local.sh && export PLATFORMIO_CORE_DIR=\"$HOME/.platformio\" && ZACUS_REQUIRE_HW=1 ./tools/dev/run_matrix_and_smoke.sh --skip-build --skip-upload"
|
||||
],
|
||||
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||
"problemMatcher": [],
|
||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||
},
|
||||
@@ -58,8 +49,9 @@
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-lc",
|
||||
"cd hardware/firmware && ./tools/dev/bootstrap_local.sh && while true; do echo \"==== $(date) ====\"; ./.venv/bin/python -m serial.tools.list_ports -v || true; echo; sleep 15; done"
|
||||
"while true; do printf '==== %s ====\\n' \"$(date)\"; ./tools/dev/zacus.sh ports; sleep 15; done"
|
||||
],
|
||||
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||
"problemMatcher": [],
|
||||
"presentation": { "panel": "dedicated", "group": "zacus-watch", "reveal": "always" }
|
||||
},
|
||||
@@ -69,8 +61,9 @@
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-lc",
|
||||
"cd hardware/firmware && while true; do d=$(ls -1dt artifacts/rc_live/* 2>/dev/null | head -n 1); if [ -n \"$d\" ]; then echo \"== $d ==\"; tail -n 60 \"$d/run_matrix_and_smoke.log\" 2>/dev/null || true; echo; fi; sleep 5; done"
|
||||
"while true; do d=$(ls -1dt artifacts/rc_live/* 2>/dev/null | head -n 1); if [ -n \"$d\" ]; then echo \"== $d ==\"; tail -n 60 \"$d/run_matrix_and_smoke.log\" 2>/dev/null || true; echo; fi; sleep 5; done"
|
||||
],
|
||||
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||
"problemMatcher": [],
|
||||
"presentation": { "panel": "dedicated", "group": "zacus-watch", "reveal": "always" }
|
||||
},
|
||||
@@ -80,8 +73,9 @@
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-lc",
|
||||
"cd hardware/firmware && d=$(ls -1dt artifacts/rc_live/* 2>/dev/null | head -n 1); echo \"Last: ${d:-n/a}\"; [ -n \"$d\" ] && sed -n '1,220p' \"$d/summary.md\" || true"
|
||||
"d=$(ls -1dt artifacts/rc_live/* 2>/dev/null | head -n 1); echo \"Last: ${d:-n/a}\"; [ -n \"$d\" ] && sed -n '1,220p' \"$d/summary.md\" || true"
|
||||
],
|
||||
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||
"problemMatcher": [],
|
||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||
},
|
||||
@@ -90,8 +84,12 @@
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": ["-lc", "echo \"Cockpit started (stop tasks to close watchers).\""],
|
||||
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||
"dependsOrder": "parallel",
|
||||
"dependsOn": ["Zacus: Firmware · Ports watch (15s)", "Zacus: Firmware · Tail last RC log (5s)"],
|
||||
"dependsOn": [
|
||||
"Zacus: Firmware · Ports watch (15s)",
|
||||
"Zacus: Firmware · Tail last RC log (5s)"
|
||||
],
|
||||
"problemMatcher": [],
|
||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ Dossier : `printables/`
|
||||
- `docs/index.md` + `docs/repo-status.md` pour la navigation et l’état du dépôt
|
||||
- `include-humain-IA/` (contenus annexes, renommé pour la portabilité)
|
||||
- `tools/dev/zacus.sh codex --prompt tools/dev/codex_prompts/zacus_overhaul_one_shot.md` lance le prompt de Codex directement depuis le dépôt.
|
||||
- Les artifacts de résolution de ports sont écrits sous `artifacts/ports/<timestamp>/ports_resolve.json` avec un pointeur `artifacts/ports/latest_ports_resolve.json`. Utilisez `ZACUS_MOCK_PORTS=1` (et `ZACUS_PORTS_FIXTURE=tools/test/fixtures/ports_list_macos.txt`) pour tester la boucle sans matériel.
|
||||
- `tools/dev/zacus.sh codex --prompt tools/dev/codex_prompts/zacus_overhaul_one_shot.md` lance le prompt de Codex directement depuis le dépôt.
|
||||
|
||||
## État du projet
|
||||
- **Kit MJ** : complet (script, plan, antic-chaos, solution, checklists, export PDF).
|
||||
|
||||
@@ -51,6 +51,10 @@ python3 tools/test/run_serial_suite.py --suite smoke_plus --allow-no-hardware
|
||||
python3 tools/test/zacus_menu.py --action smoke --allow-no-hardware
|
||||
```
|
||||
|
||||
Pour résoudre les ports sans matériel, la commande `./tools/dev/zacus.sh ports` écrit le JSON contractuel dans `artifacts/ports/<timestamp>/ports_resolve.json` et met à jour `artifacts/ports/latest_ports_resolve.json`. Pour mocker les CP2102 utilisez `ZACUS_MOCK_PORTS=1 ZACUS_PORTS_FIXTURE=tools/test/fixtures/ports_list_macos.txt ./tools/dev/zacus.sh ports`.
|
||||
|
||||
Codex CLI intégré : `./tools/dev/zacus.sh codex --prompt tools/dev/codex_prompts/zacus_overhaul_one_shot.md`. Le script note aussi l’état des ports résolus et place les logs dans `artifacts/codex/<timestamp>/`.
|
||||
|
||||
Dépendances optionnelles:
|
||||
- `pip install pyyaml` pour `run_content_checks.sh`
|
||||
- `pip install pyserial` pour suites USB, UI Link sim et console série
|
||||
|
||||
Vendored
+9
-11
@@ -5,10 +5,7 @@
|
||||
"label": "Zacus: RC Live",
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-lc",
|
||||
"set -euo pipefail; ROOT=$(git rev-parse --show-toplevel); \"$ROOT/tools/dev/zacus.sh\" rc"
|
||||
],
|
||||
"args": ["-lc", "set -euo pipefail; ./tools/dev/zacus.sh rc"],
|
||||
"options": { "cwd": "${workspaceFolder}" },
|
||||
"presentation": { "panel": "dedicated", "reveal": "always" },
|
||||
"problemMatcher": []
|
||||
@@ -19,7 +16,7 @@
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-lc",
|
||||
"set -euo pipefail; ROOT=$(git rev-parse --show-toplevel); \"$ROOT/tools/dev/zacus.sh\" flash && \"$ROOT/tools/dev/zacus.sh\" rc"
|
||||
"set -euo pipefail; ./tools/dev/zacus.sh flash && ./tools/dev/zacus.sh rc"
|
||||
],
|
||||
"options": { "cwd": "${workspaceFolder}" },
|
||||
"presentation": { "panel": "dedicated", "reveal": "always" },
|
||||
@@ -29,10 +26,7 @@
|
||||
"label": "Zacus: RC Live + AutoFix",
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-lc",
|
||||
"set -euo pipefail; ROOT=$(git rev-parse --show-toplevel); \"$ROOT/tools/dev/zacus.sh\" rc-autofix"
|
||||
],
|
||||
"args": ["-lc", "set -euo pipefail; ./tools/dev/zacus.sh rc-autofix"],
|
||||
"options": { "cwd": "${workspaceFolder}" },
|
||||
"presentation": { "panel": "dedicated", "reveal": "always" },
|
||||
"problemMatcher": []
|
||||
@@ -43,10 +37,14 @@
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-lc",
|
||||
"set -euo pipefail; ROOT=$(git rev-parse --show-toplevel); \"$ROOT/tools/dev/zacus.sh\" ports"
|
||||
"set -euo pipefail; while true; do ./tools/dev/zacus.sh ports; sleep 15; done"
|
||||
],
|
||||
"options": { "cwd": "${workspaceFolder}" },
|
||||
"presentation": { "panel": "dedicated", "reveal": "always", "showReuseMessage": false },
|
||||
"presentation": {
|
||||
"panel": "dedicated",
|
||||
"reveal": "always",
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
# --- Codex Prompt Menu TUI ---
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(git -C "$SCRIPT_DIR/.." rev-parse --show-toplevel)"
|
||||
FW_ROOT="$REPO_ROOT/hardware/firmware"
|
||||
@@ -8,6 +10,15 @@ PROMPT_DIR="$FW_ROOT/tools/dev/codex_prompts"
|
||||
ARTIFACTS_DIR="$FW_ROOT/artifacts/rc_live"
|
||||
LAST_MESSAGE_FILE="$ARTIFACTS_DIR/_codex_last_message.md"
|
||||
|
||||
# Détection TUI (dialog/whiptail)
|
||||
if command -v dialog >/dev/null 2>&1; then
|
||||
TUI_CMD="dialog"
|
||||
elif command -v whiptail >/dev/null 2>&1; then
|
||||
TUI_CMD="whiptail"
|
||||
else
|
||||
TUI_CMD=""
|
||||
fi
|
||||
|
||||
collect_prompts() {
|
||||
local glob
|
||||
prompt_files=()
|
||||
@@ -17,6 +28,8 @@ collect_prompts() {
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
# Menu interactif pour choisir un prompt (TUI si possible)
|
||||
main_menu() {
|
||||
collect_prompts
|
||||
if [[ ${#prompt_files[@]} -eq 0 ]]; then
|
||||
@@ -24,25 +37,43 @@ main_menu() {
|
||||
exit 1
|
||||
fi
|
||||
while true; do
|
||||
echo
|
||||
echo "Available prompts:"
|
||||
for idx in "${!prompt_files[@]}"; do
|
||||
printf "%2d) %s\n" $((idx + 1)) "$(basename "${prompt_files[idx]}")"
|
||||
done
|
||||
read -rp "Choice (q to quit): " choice
|
||||
if [[ "$choice" =~ ^[Qq]$ ]]; then
|
||||
exit 0
|
||||
local choice
|
||||
if [[ -n "$TUI_CMD" ]]; then
|
||||
local menu_args=()
|
||||
for idx in "${!prompt_files[@]}"; do
|
||||
menu_args+=("$idx" "$(basename "${prompt_files[idx]}")")
|
||||
done
|
||||
choice=$( \
|
||||
$TUI_CMD --clear --title "Codex Prompt Menu" \
|
||||
--menu "Sélectionnez un prompt :" 20 70 12 \
|
||||
"${menu_args[@]}" \
|
||||
3>&1 1>&2 2>&3
|
||||
)
|
||||
if [[ -z "$choice" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
run_prompt "${prompt_files[choice]}"
|
||||
else
|
||||
echo
|
||||
echo "Available prompts:"
|
||||
for idx in "${!prompt_files[@]}"; do
|
||||
printf "%2d) %s\n" $((idx + 1)) "$(basename "${prompt_files[idx]}")"
|
||||
done
|
||||
read -rp "Choice (q to quit): " choice
|
||||
if [[ "$choice" =~ ^[Qq]$ ]]; then
|
||||
exit 0
|
||||
fi
|
||||
if ! [[ "$choice" =~ ^[0-9]+$ ]]; then
|
||||
echo "Please enter a number or 'q'."
|
||||
continue
|
||||
fi
|
||||
choice=$((choice - 1))
|
||||
if (( choice < 0 || choice >= ${#prompt_files[@]} )); then
|
||||
echo "Selection out of range."
|
||||
continue
|
||||
fi
|
||||
run_prompt "${prompt_files[choice]}"
|
||||
fi
|
||||
if ! [[ "$choice" =~ ^[0-9]+$ ]]; then
|
||||
echo "Please enter a number or 'q'."
|
||||
continue
|
||||
fi
|
||||
choice=$((choice - 1))
|
||||
if (( choice < 0 || choice >= ${#prompt_files[@]} )); then
|
||||
echo "Selection out of range."
|
||||
continue
|
||||
fi
|
||||
run_prompt "${prompt_files[choice]}"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
# --- Zacus.sh TUI ---
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
ARTIFACT_ROOT="$REPO_ROOT/artifacts/rc_live"
|
||||
@@ -8,6 +10,15 @@ RESOLVER="$REPO_ROOT/tools/test/resolve_ports.py"
|
||||
RC_RUNNER="$REPO_ROOT/tools/dev/run_matrix_and_smoke.sh"
|
||||
PROMPT_DIR="$SCRIPT_DIR/codex_prompts"
|
||||
|
||||
# Détection TUI (dialog/whiptail)
|
||||
if command -v dialog >/dev/null 2>&1; then
|
||||
TUI_CMD="dialog"
|
||||
elif command -v whiptail >/dev/null 2>&1; then
|
||||
TUI_CMD="whiptail"
|
||||
else
|
||||
TUI_CMD=""
|
||||
fi
|
||||
|
||||
require_cmd() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "[zacus] missing command: $1" >&2
|
||||
@@ -193,10 +204,36 @@ HELP
|
||||
|
||||
mkdir -p "$ARTIFACT_ROOT"
|
||||
|
||||
|
||||
# Menu interactif si aucun argument fourni
|
||||
command=${1:-}
|
||||
if [[ -z "$command" ]]; then
|
||||
usage
|
||||
exit 1
|
||||
if [[ -n "$TUI_CMD" ]]; then
|
||||
choice=$( \
|
||||
$TUI_CMD --clear --title "Zacus CLI" \
|
||||
--menu "Sélectionnez une commande :" 20 70 10 \
|
||||
bootstrap "bootstrap tooling" \
|
||||
build "run build_all.sh" \
|
||||
flash "upload esp32 + esp8266 via resolved ports" \
|
||||
rc "strict RC live (ZACUS_REQUIRE_HW=1)" \
|
||||
rc-autofix "RC + codex autofix loop" \
|
||||
ports "ports watch (15s)" \
|
||||
latest "show latest RC artifact path" \
|
||||
exit "exit" \
|
||||
3>&1 1>&2 2>&3
|
||||
)
|
||||
if [[ -z "$choice" || "$choice" == "exit" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
command="$choice"
|
||||
else
|
||||
echo "Usage: zacus.sh <command>"
|
||||
echo "Commandes disponibles : bootstrap, build, flash, rc, rc-autofix, ports, latest"
|
||||
read -rp "Commande : " command
|
||||
if [[ -z "$command" || "$command" == "exit" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$command" in
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# DEBUG: Affichage de début de script
|
||||
echo "[DEBUG] Lancement du cockpit Zacus..." >&2
|
||||
sleep 0.1
|
||||
export LC_ALL=C
|
||||
export LANG=C
|
||||
|
||||
|
||||
# --- Zacus Menu TUI ---
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(git -C "$SCRIPT_DIR/../.." rev-parse --show-toplevel)"
|
||||
FW_ROOT="$REPO_ROOT/hardware/firmware"
|
||||
@@ -8,17 +17,36 @@ ARTIFACTS_ROOT="$FW_ROOT/artifacts/rc_live"
|
||||
PROMPT_DIR="$FW_ROOT/tools/dev/codex_prompts"
|
||||
RC_PROMPT="$PROMPT_DIR/rc_live_fail.prompt.md"
|
||||
|
||||
# Détection TUI (fzf > dialog > whiptail)
|
||||
if command -v fzf >/dev/null 2>&1; then
|
||||
TUI_CMD="fzf"
|
||||
elif command -v dialog >/dev/null 2>&1; then
|
||||
TUI_CMD="dialog"
|
||||
elif command -v whiptail >/dev/null 2>&1; then
|
||||
TUI_CMD="whiptail"
|
||||
else
|
||||
TUI_CMD=""
|
||||
fi
|
||||
|
||||
# Message d'invitation à installer fzf si aucun TUI
|
||||
if [[ -z "$TUI_CMD" ]]; then
|
||||
echo -e "\033[1;33m[Astuce] Pour une navigation moderne (flèches + Entrée/Échap), installez fzf :\033[0m" >&2
|
||||
echo -e "\033[1;36mbrew install fzf\033[0m (ou sudo apt install fzf sous Linux)" >&2
|
||||
fi
|
||||
|
||||
|
||||
|
||||
run_bootstrap() {
|
||||
(cd "$REPO_ROOT" && ./tools/dev/bootstrap_local.sh)
|
||||
"$FW_ROOT/tools/dev/bootstrap_local.sh"
|
||||
}
|
||||
|
||||
run_build_all() {
|
||||
(cd "$FW_ROOT" && ./build_all.sh)
|
||||
"$FW_ROOT/build_all.sh"
|
||||
}
|
||||
|
||||
run_rc_live() {
|
||||
local artifacts=""
|
||||
if ! (cd "$FW_ROOT" && ./tools/dev/run_matrix_and_smoke.sh); then
|
||||
if ! "$FW_ROOT/tools/dev/run_matrix_and_smoke.sh"; then
|
||||
artifacts="$(latest_artifacts)"
|
||||
if [[ -n "$artifacts" && -f "$RC_PROMPT" ]]; then
|
||||
ARTIFACT_PATH="$artifacts" codex exec - < "$RC_PROMPT"
|
||||
@@ -46,34 +74,212 @@ latest_artifacts() {
|
||||
ls -1d "$ARTIFACTS_ROOT"/*/ 2>/dev/null | sort | tail -n1
|
||||
}
|
||||
|
||||
menu() {
|
||||
cat <<'EOF'
|
||||
Firmware cockpit
|
||||
1) bootstrap (tools/dev/bootstrap_local.sh)
|
||||
2) build all firmware (hardware/firmware/build_all.sh)
|
||||
3) rc live gate (ZACUS_REQUIRE_HW unset)
|
||||
4) rc live gate (ZACUS_REQUIRE_HW=1)
|
||||
5) watch serial ports
|
||||
6) run codex prompt menu
|
||||
0) exit
|
||||
EOF
|
||||
|
||||
# Affichage du menu (fzf > dialog > texte coloré)
|
||||
show_menu() {
|
||||
local options=(
|
||||
"1:RC live gate (ZACUS_REQUIRE_HW unset)"
|
||||
"2:RC live gate (ZACUS_REQUIRE_HW=1)"
|
||||
"3:Build all firmware (hardware/firmware/build_all.sh)"
|
||||
"4:Bootstrap (outils/dev/bootstrap_local.sh)"
|
||||
"5:Watch serial ports"
|
||||
"6:Run codex prompt menu"
|
||||
"7:Afficher logs (firmware/logs/)"
|
||||
"0:Exit"
|
||||
)
|
||||
if [[ "$TUI_CMD" == "fzf" ]]; then
|
||||
local choice
|
||||
choice=$(printf '%s\n' "${options[@]}" | fzf --ansi --prompt="❯ Zacus Firmware Cockpit : " --header="[Flèches] naviguer, [Entrée] valider, [Échap] quitter" --height=15 --border --cycle)
|
||||
[[ -z "$choice" ]] && echo 0 && return
|
||||
echo "${choice%%:*}"
|
||||
elif [[ "$TUI_CMD" == "dialog" || "$TUI_CMD" == "whiptail" ]]; then
|
||||
local menu_args=()
|
||||
for opt in "${options[@]}"; do
|
||||
menu_args+=("${opt%%:*}" "${opt#*:}")
|
||||
done
|
||||
local choice
|
||||
if [[ "$TUI_CMD" == "dialog" ]]; then
|
||||
choice=$(dialog --clear --colors --title "\Zb\Z4Zacus Firmware Cockpit\Zn" \
|
||||
--menu "\Z1Sélectionnez une action :\Zn" 20 70 10 \
|
||||
"${menu_args[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
else
|
||||
choice=$(whiptail --title "Zacus Firmware Cockpit" \
|
||||
--menu "Sélectionnez une action :" 20 70 10 \
|
||||
"${menu_args[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
fi
|
||||
[[ -z "$choice" ]] && echo 0 && return
|
||||
echo "$choice"
|
||||
else
|
||||
# Fallback texte coloré, toujours affiché tant qu'un choix valide n'est pas fait
|
||||
local Y="\033[1;33m" G="\033[1;32m" C="\033[1;36m" R="\033[1;31m" N="\033[0m"
|
||||
echo -e "${R}[Aucun menu TUI détecté : utilisation du mode texte interactif]${N}" >&2; >&2
|
||||
echo "[DEBUG] TEST affichage menu texte" >&2; >&2
|
||||
trap 'echo; exit 0' INT
|
||||
while true; do
|
||||
echo -e "${C}╔══════════════════════════════════════════════════════╗${N}" >&2; >&2
|
||||
echo -e "${C}║ ${G}Zacus Firmware Cockpit (mode texte)${C} ║${N}" >&2; >&2
|
||||
echo -e "${C}╚══════════════════════════════════════════════════════╝${N}" >&2; >&2
|
||||
echo -e "${Y}Navigation : Entrez le numéro puis [Entrée]. Annulez avec Ctrl+C.${N}" >&2; >&2
|
||||
echo -e "${Y}Pour une navigation à la souris ou aux flèches, installez fzf !${N}" >&2; >&2
|
||||
for opt in "${options[@]}"; do
|
||||
printf " ${Y}%s${N} %s\n" "${opt%%:*}" "${opt#*:}" >&2; >&2
|
||||
done
|
||||
echo -en "${G}Votre choix [0-7] : ${N}" >&2; >&2
|
||||
read -r choice || { echo; exit 0; }
|
||||
[[ -z "$choice" || "$choice" == "0" ]] && echo 0 && return
|
||||
if [[ "$choice" =~ ^[1-7]$ ]]; then
|
||||
echo "$choice"
|
||||
return
|
||||
fi
|
||||
echo -e "${R}Entrée invalide. Veuillez choisir un numéro entre 0 et 7.${N}" >&2; >&2
|
||||
done
|
||||
# Sous-menu pour afficher les logs
|
||||
afficher_logs_menu() {
|
||||
local logs_dir="$FW_ROOT/logs"
|
||||
local files=()
|
||||
local file
|
||||
# Liste les fichiers de log (récents d'abord)
|
||||
while IFS= read -r -d '' f; do
|
||||
files+=("$f")
|
||||
done < <(find "$logs_dir" -type f -print0 | sort -z -r)
|
||||
if [[ ${#files[@]} -eq 0 ]]; then
|
||||
echo "Aucun fichier de log trouvé dans $logs_dir." >&2
|
||||
sleep 2
|
||||
return
|
||||
fi
|
||||
local choix
|
||||
if [[ "$TUI_CMD" == "fzf" ]]; then
|
||||
choix=$(printf '%s\n' "${files[@]}" | fzf --ansi --prompt="❯ Sélectionnez un log : " --header="[Flèches] naviguer, [Entrée] afficher, [Échap] retour" --height=15 --border)
|
||||
[[ -z "$choix" ]] && return
|
||||
elif [[ "$TUI_CMD" == "dialog" || "$TUI_CMD" == "whiptail" ]]; then
|
||||
local menu_args=()
|
||||
local idx=1
|
||||
for f in "${files[@]}"; do
|
||||
menu_args+=("$idx" "$(basename "$f")")
|
||||
((idx++))
|
||||
done
|
||||
if [[ "$TUI_CMD" == "dialog" ]]; then
|
||||
idx=$(dialog --clear --title "Logs firmware" --menu "Sélectionnez un log :" 20 70 15 "${menu_args[@]}" 3>&1 1>&2 2>&3)
|
||||
else
|
||||
idx=$(whiptail --title "Logs firmware" --menu "Sélectionnez un log :" 20 70 15 "${menu_args[@]}" 3>&1 1>&2 2>&3)
|
||||
fi
|
||||
[[ -z "$idx" ]] && return
|
||||
choix="${files[idx-1]}"
|
||||
else
|
||||
echo -e "\033[1;36mFichiers de log disponibles :\033[0m" >&2
|
||||
local i=1
|
||||
for f in "${files[@]}"; do
|
||||
echo " $i) $(basename "$f")" >&2
|
||||
((i++))
|
||||
done
|
||||
echo -en "Numéro du log à afficher (ou Entrée pour retour) : " >&2
|
||||
read -r idx
|
||||
[[ -z "$idx" ]] && return
|
||||
if [[ "$idx" =~ ^[0-9]+$ ]] && (( idx >= 1 && idx <= ${#files[@]} )); then
|
||||
choix="${files[idx-1]}"
|
||||
else
|
||||
echo "Entrée invalide." >&2
|
||||
sleep 1
|
||||
return
|
||||
fi
|
||||
fi
|
||||
echo -e "\033[1;33m--- Dernières lignes de : $choix ---\033[0m"
|
||||
tail -n 15 "$choix"
|
||||
echo -e "\033[1;36m--- Appuyez sur Entrée pour revenir au menu ---\033[0m"
|
||||
read -r _
|
||||
}
|
||||
fi
|
||||
}
|
||||
|
||||
run_codex_prompts() {
|
||||
"$FW_ROOT/tools/dev/codex_prompt_menu.sh"
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Sous-menu pour afficher les logs (doit être défini avant la boucle principale)
|
||||
afficher_logs_menu() {
|
||||
local logs_dir="$FW_ROOT/logs"
|
||||
local files=()
|
||||
local file
|
||||
# Liste les fichiers de log (récents d'abord)
|
||||
while IFS= read -r -d '' f; do
|
||||
files+=("$f")
|
||||
done < <(find "$logs_dir" -type f -print0 | sort -z -r)
|
||||
if [[ ${#files[@]} -eq 0 ]]; then
|
||||
echo "Aucun fichier de log trouvé dans $logs_dir." >&2
|
||||
sleep 2
|
||||
return
|
||||
fi
|
||||
local choix
|
||||
if [[ "$TUI_CMD" == "fzf" ]]; then
|
||||
choix=$(printf '%s\n' "${files[@]}" | fzf --ansi --prompt="❯ Sélectionnez un log : " --header="[Flèches] naviguer, [Entrée] afficher, [Échap] retour" --height=15 --border)
|
||||
[[ -z "$choix" ]] && return
|
||||
elif [[ "$TUI_CMD" == "dialog" || "$TUI_CMD" == "whiptail" ]]; then
|
||||
local menu_args=()
|
||||
local idx=1
|
||||
for f in "${files[@]}"; do
|
||||
menu_args+=("$idx" "$(basename "$f")")
|
||||
((idx++))
|
||||
done
|
||||
if [[ "$TUI_CMD" == "dialog" ]]; then
|
||||
idx=$(dialog --clear --title "Logs firmware" --menu "Sélectionnez un log :" 20 70 15 "${menu_args[@]}" 3>&1 1>&2 2>&3)
|
||||
else
|
||||
idx=$(whiptail --title "Logs firmware" --menu "Sélectionnez un log :" 20 70 15 "${menu_args[@]}" 3>&1 1>&2 2>&3)
|
||||
fi
|
||||
[[ -z "$idx" ]] && return
|
||||
choix="${files[idx-1]}"
|
||||
else
|
||||
echo -e "\033[1;36mFichiers de log disponibles :\033[0m" >&2
|
||||
local i=1
|
||||
for f in "${files[@]}"; do
|
||||
echo " $i) $(basename "$f")" >&2
|
||||
((i++))
|
||||
done
|
||||
echo -en "Numéro du log à afficher (ou Entrée pour retour) : " >&2
|
||||
read -r idx
|
||||
[[ -z "$idx" ]] && return
|
||||
if [[ "$idx" =~ ^[0-9]+$ ]] && (( idx >= 1 && idx <= ${#files[@]} )); then
|
||||
choix="${files[idx-1]}"
|
||||
else
|
||||
echo "Entrée invalide." >&2
|
||||
sleep 1
|
||||
return
|
||||
fi
|
||||
fi
|
||||
echo -e "\033[1;33m--- Dernières lignes de : $choix ---\033[0m"
|
||||
tail -n 15 "$choix"
|
||||
echo -e "\033[1;36m--- Appuyez sur Entrée pour revenir au menu ---\033[0m"
|
||||
read -r _
|
||||
}
|
||||
|
||||
# Boucle principale avec menu TUI amélioré
|
||||
|
||||
# Forçage du menu texte si aucun TUI détecté
|
||||
if [[ -z "$TUI_CMD" ]]; then
|
||||
echo "[INFO] Aucun menu TUI détecté, utilisation du menu texte interactif." >&2
|
||||
fi
|
||||
|
||||
while true; do
|
||||
menu
|
||||
read -rp "Choice: " choice
|
||||
echo "[DEBUG] Affichage du menu..." >&2
|
||||
choice=$(show_menu)
|
||||
echo "[DEBUG] Choix utilisateur: '$choice'" >&2
|
||||
case "$choice" in
|
||||
1) run_bootstrap ;;
|
||||
2) run_build_all ;;
|
||||
3) ZACUS_REQUIRE_HW=0 run_rc_live ;;
|
||||
4) ZACUS_REQUIRE_HW=1 run_rc_live ;;
|
||||
1) ZACUS_REQUIRE_HW=0 run_rc_live ;;
|
||||
2) ZACUS_REQUIRE_HW=1 run_rc_live ;;
|
||||
3) run_build_all ;;
|
||||
4) run_bootstrap ;;
|
||||
5) ports_watch ;;
|
||||
6) run_codex_prompts ;;
|
||||
0) exit 0 ;;
|
||||
*) echo "Unknown option: $choice" ;;
|
||||
7) afficher_logs_menu ;;
|
||||
0|""|"Cancel")
|
||||
echo "[DEBUG] Sortie du cockpit Zacus." >&2
|
||||
[[ "$TUI_CMD" == "dialog" ]] && dialog --msgbox "Sortie du cockpit Zacus." 6 40
|
||||
exit 0 ;;
|
||||
*)
|
||||
echo "[DEBUG] Entrée non reconnue, relance du menu." >&2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
+130
-38
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
FW_ROOT="$REPO_ROOT/hardware/firmware"
|
||||
RC_RUNNER="$FW_ROOT/tools/dev/run_matrix_and_smoke.sh"
|
||||
SMOKE_SCRIPT="$FW_ROOT/tools/dev/serial_smoke.py"
|
||||
@@ -11,6 +11,7 @@ ARTIFACT_ROOT="$REPO_ROOT/artifacts"
|
||||
PORTS_ARTIFACT_ROOT="$ARTIFACT_ROOT/ports"
|
||||
LATEST_PORTS_JSON="$PORTS_ARTIFACT_ROOT/latest_ports_resolve.json"
|
||||
CODEX_ARTIFACT_ROOT="$ARTIFACT_ROOT/codex"
|
||||
DEFAULT_PORTS_FIXTURE="$REPO_ROOT/tools/test/fixtures/ports_list_macos.txt"
|
||||
LAST_RESOLVED_JSON=""
|
||||
|
||||
info() {
|
||||
@@ -22,15 +23,20 @@ die() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
die_codex() {
|
||||
printf '[zacus] ERROR: %s\n' "$*" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: zacus.sh <command> [args]
|
||||
Commands:
|
||||
rc Run the RC live gate (requires hardware)
|
||||
rc Run the RC live gate (always strict)
|
||||
smoke Run the serial smoke helper (pass --role ... args)
|
||||
ports Resolve ports once and show summary
|
||||
codex Invoke codex exec with a prompt (see --help)
|
||||
menu Show the zacus cockpit menu
|
||||
codex Invoke Codex CLI with a prompt
|
||||
menu Show the Zacus cockpit menu
|
||||
help Show this usage
|
||||
USAGE
|
||||
}
|
||||
@@ -41,24 +47,38 @@ print_ports_summary() {
|
||||
printf '[ports] no resolved data yet (run "zacus.sh ports" to resolve)\n'
|
||||
return 1
|
||||
fi
|
||||
python3 - <<'PY'
|
||||
python3 - "$json_file" <<'PY'
|
||||
import datetime
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
path = pathlib.Path(sys.argv[1])
|
||||
data = json.loads(path.read_text(encoding='utf-8'))
|
||||
ports = data.get('ports', {})
|
||||
reasons = data.get('reasons', {})
|
||||
notes = data.get('notes', [])
|
||||
labels = [('esp32', 'esp32_audio'), ('esp8266', 'esp8266_oled')]
|
||||
print(f'[ports] summary ({path.name}):')
|
||||
for role, label in labels:
|
||||
port = ports.get(role) or 'n/a'
|
||||
reason = reasons.get(role) or 'unset'
|
||||
print(f' - {label}: {port} (reason={reason})')
|
||||
print(' - rp2040_tft: not auto-resolved (manual selection)')
|
||||
print('[ports] notes:', ' | '.join(notes) if notes else 'none')
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding='utf-8'))
|
||||
ports = data.get('ports', {})
|
||||
reasons = data.get('reasons', {})
|
||||
notes = data.get('notes', [])
|
||||
labels = [('esp32', 'esp32_audio'), ('esp8266', 'esp8266_oled')]
|
||||
timestamp = path.parent.name
|
||||
age = 'unknown'
|
||||
try:
|
||||
dt = datetime.datetime.strptime(timestamp, '%Y%m%d-%H%M%S')
|
||||
dt = dt.replace(tzinfo=datetime.timezone.utc)
|
||||
age_delta = datetime.datetime.now(datetime.timezone.utc) - dt
|
||||
age = str(age_delta).split('.')[0]
|
||||
except Exception:
|
||||
pass
|
||||
print(f'[ports] summary (json={path.name}) timestamp={timestamp} age={age}')
|
||||
for role, label in labels:
|
||||
port = ports.get(role) or 'n/a'
|
||||
reason = reasons.get(role) or 'unset'
|
||||
print(f' - {label}: {port} (reason={reason})')
|
||||
print(' - rp2040_tft: not auto-resolved (manual selection)')
|
||||
print(f'[ports] json path: {path}')
|
||||
print('[ports] notes:', ' | '.join(notes) if notes else 'none')
|
||||
except Exception as exc:
|
||||
print(f'[ports] summary unavailable: {exc}')
|
||||
PY
|
||||
}
|
||||
|
||||
@@ -69,24 +89,64 @@ resolve_ports_once() {
|
||||
local dir="$PORTS_ARTIFACT_ROOT/$timestamp"
|
||||
mkdir -p "$dir"
|
||||
local json="$dir/ports_resolve.json"
|
||||
local resolver_args=(python3 "$REPO_ROOT/tools/test/resolve_ports.py" \
|
||||
--need-esp32 --need-esp8266 --ports-resolve-json "$json" --wait-port 3)
|
||||
local use_fixture=0
|
||||
local fixture_path=""
|
||||
if [[ "${ZACUS_MOCK_PORTS:-0}" == "1" ]]; then
|
||||
use_fixture=1
|
||||
fixture_path="${ZACUS_PORTS_FIXTURE:-$DEFAULT_PORTS_FIXTURE}"
|
||||
elif [[ -n "${ZACUS_PORTS_FIXTURE:-}" ]]; then
|
||||
use_fixture=1
|
||||
fixture_path="${ZACUS_PORTS_FIXTURE}"
|
||||
fi
|
||||
if (( use_fixture == 1 )); then
|
||||
resolver_args+=(--mock)
|
||||
if [[ -n "$fixture_path" && -f "$fixture_path" ]]; then
|
||||
resolver_args+=(--ports-fixture "$fixture_path")
|
||||
else
|
||||
info "ports fixture not found: $fixture_path"
|
||||
fi
|
||||
fi
|
||||
info "resolving ports (output -> $json)"
|
||||
set +e
|
||||
python3 "$REPO_ROOT/tools/test/resolve_ports.py" \
|
||||
--need-esp32 --need-esp8266 --ports-resolve-json "$json" --wait-port 3
|
||||
"${resolver_args[@]}"
|
||||
local rc=$?
|
||||
set -e
|
||||
if [[ -f "$json" ]]; then
|
||||
cp "$json" "$LATEST_PORTS_JSON" 2>/dev/null || true
|
||||
LAST_RESOLVED_JSON="$json"
|
||||
fi
|
||||
LAST_RESOLVED_JSON="$json"
|
||||
return $rc
|
||||
}
|
||||
|
||||
show_ports_context() {
|
||||
local candidate_json
|
||||
if [[ -n "${LAST_RESOLVED_JSON}" && -f "$LAST_RESOLVED_JSON" ]]; then
|
||||
candidate_json="$LAST_RESOLVED_JSON"
|
||||
elif [[ -f "$LATEST_PORTS_JSON" ]]; then
|
||||
candidate_json="$LATEST_PORTS_JSON"
|
||||
else
|
||||
printf '[ports] no resolution data yet\n'
|
||||
return 1
|
||||
fi
|
||||
print_ports_summary "$candidate_json" || true
|
||||
if [[ -f "$LATEST_PORTS_JSON" ]]; then
|
||||
printf '[ports] pointer: %s\n' "$LATEST_PORTS_JSON"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_ports() {
|
||||
resolve_ports_once || true
|
||||
if [[ -n "${LAST_RESOLVED_JSON:-}" ]]; then
|
||||
print_ports_summary "$LAST_RESOLVED_JSON" || true
|
||||
printf '[ports] data file: %s\n' "$LAST_RESOLVED_JSON"
|
||||
local json_file=""
|
||||
if [[ -n "${LAST_RESOLVED_JSON}" && -f "$LAST_RESOLVED_JSON" ]]; then
|
||||
json_file="$LAST_RESOLVED_JSON"
|
||||
elif [[ -f "$LATEST_PORTS_JSON" ]]; then
|
||||
json_file="$LATEST_PORTS_JSON"
|
||||
fi
|
||||
if [[ -n "$json_file" ]]; then
|
||||
print_ports_summary "$json_file" || true
|
||||
printf '[ports] data file: %s\n' "$json_file"
|
||||
else
|
||||
printf '[ports] resolver did not produce a JSON file\n'
|
||||
fi
|
||||
@@ -107,10 +167,10 @@ cmd_smoke() {
|
||||
|
||||
ensure_codex_ready() {
|
||||
if ! command -v codex >/dev/null 2>&1; then
|
||||
die "codex CLI not found. Install it from https://app.codex.com/docs/cli"
|
||||
die_codex "codex CLI not found. Install it from https://app.codex.com/docs/cli"
|
||||
fi
|
||||
if ! codex login status >/dev/null 2>&1; then
|
||||
die "codex CLI is not logged in. Run 'codex login' before continuing."
|
||||
die_codex "codex CLI is not logged in. Run 'codex login' before continuing."
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -118,7 +178,6 @@ cmd_codex() {
|
||||
local prompt_file=""
|
||||
local sandbox="workspace-write"
|
||||
local auto_mode="0"
|
||||
local custom_sandbox=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--prompt)
|
||||
@@ -154,16 +213,17 @@ HELP
|
||||
fi
|
||||
ensure_codex_ready
|
||||
resolve_ports_once || true
|
||||
if [[ -n "${LAST_RESOLVED_JSON:-}" ]]; then
|
||||
print_ports_summary "$LAST_RESOLVED_JSON" || true
|
||||
local ports_json="${LAST_RESOLVED_JSON:-$LATEST_PORTS_JSON}"
|
||||
if [[ -n "$ports_json" && -f "$ports_json" ]]; then
|
||||
print_ports_summary "$ports_json" || true
|
||||
fi
|
||||
mkdir -p "$CODEX_ARTIFACT_ROOT"
|
||||
local ts
|
||||
ts=$(date -u +%Y%m%d-%H%M%S)
|
||||
local art_dir="$CODEX_ARTIFACT_ROOT/$ts"
|
||||
mkdir -p "$art_dir"
|
||||
if [[ -f "$LAST_RESOLVED_JSON" ]]; then
|
||||
cp "$LAST_RESOLVED_JSON" "$art_dir/ports_resolve.json" 2>/dev/null || true
|
||||
if [[ -n "$ports_json" && -f "$ports_json" ]]; then
|
||||
cp "$ports_json" "$art_dir/ports_resolve.json" 2>/dev/null || true
|
||||
fi
|
||||
printf 'prompt=%s\nsandbox=%s\nauto=%s\n' "$prompt_file" "$sandbox" "$auto_mode" > "$art_dir/metadata.txt"
|
||||
local exec_cmd=(codex exec --sandbox "$sandbox")
|
||||
@@ -241,9 +301,29 @@ menu_run_codex() {
|
||||
}
|
||||
|
||||
cmd_menu() {
|
||||
local non_tui=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--no-tui)
|
||||
non_tui=1
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
die "unknown menu option: $1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
if (( non_tui == 1 )); then
|
||||
cmd_ports
|
||||
return 0
|
||||
fi
|
||||
while true; do
|
||||
printf '\n=== Zacus cockpit ===\n'
|
||||
print_ports_summary "${LATEST_PORTS_JSON}" || true
|
||||
show_ports_context || true
|
||||
printf '[menu] options:\n'
|
||||
printf ' 1) RC live\n'
|
||||
printf ' 2) Serial smoke\n'
|
||||
@@ -252,12 +332,24 @@ cmd_menu() {
|
||||
printf ' 0) Exit\n'
|
||||
read -rp 'Choice: ' choice
|
||||
case "$choice" in
|
||||
1) cmd_rc ;;
|
||||
2) cmd_smoke ;;
|
||||
3) menu_run_codex ;;
|
||||
4) cmd_ports ;;
|
||||
0) return 0 ;;
|
||||
*) printf 'unknown choice\n' ;;
|
||||
1)
|
||||
cmd_rc
|
||||
;;
|
||||
2)
|
||||
cmd_smoke
|
||||
;;
|
||||
3)
|
||||
menu_run_codex
|
||||
;;
|
||||
4)
|
||||
cmd_ports
|
||||
;;
|
||||
0)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
printf 'unknown choice\n'
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
@@ -278,7 +370,7 @@ case "$command" in
|
||||
cmd_codex "$@"
|
||||
;;
|
||||
menu)
|
||||
cmd_menu
|
||||
cmd_menu "$@"
|
||||
;;
|
||||
help|-h|--help)
|
||||
usage
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
/dev/cu.SLAB_USBtoUART1,20-6.1.1,ESP32 dev kit,10C4:EA60
|
||||
/dev/cu.SLAB_USBtoUART2,20-6.1.2,ESP8266 OLED,10C4:EA60
|
||||
+100
-3
@@ -11,14 +11,17 @@ import re
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
import csv
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
PY_SERIAL_ERROR = ""
|
||||
try:
|
||||
import serial
|
||||
from serial.tools import list_ports
|
||||
except ImportError:
|
||||
print(json.dumps({"status": "fail", "notes": ["pyserial missing: pip install pyserial"]}))
|
||||
raise SystemExit(2)
|
||||
except ImportError as exc:
|
||||
serial = None
|
||||
list_ports = None
|
||||
PY_SERIAL_ERROR = str(exc)
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -37,6 +40,40 @@ ESP8266_SIGNATURE = re.compile(r"(ets Jan|Exception \(|Stack smashing|\[SCREEN\]
|
||||
FINGERPRINT_BAUDS = (115200, 19200)
|
||||
FINGERPRINT_TIMEOUT = 2.0
|
||||
|
||||
DEFAULT_PORTS_FIXTURE = REPO_ROOT / "tools" / "test" / "fixtures" / "ports_list_macos.txt"
|
||||
|
||||
|
||||
class MockPort:
|
||||
def __init__(
|
||||
self,
|
||||
device: str,
|
||||
location: str = "",
|
||||
description: str = "",
|
||||
manufacturer: str = "",
|
||||
product: str = "",
|
||||
vid: int = 0,
|
||||
pid: int = 0,
|
||||
) -> None:
|
||||
self.device = device
|
||||
self.location = location
|
||||
self.description = description
|
||||
self.manufacturer = manufacturer
|
||||
self.product = product
|
||||
self.vid = vid
|
||||
self.pid = pid
|
||||
|
||||
@property
|
||||
def hwid(self) -> str:
|
||||
parts = []
|
||||
if self.vid is not None and self.pid is not None:
|
||||
parts.append(f"USB VID:PID={self.vid:04X}:{self.pid:04X}")
|
||||
if self.location:
|
||||
parts.append(f"LOCATION={self.location}")
|
||||
return " ".join(parts)
|
||||
|
||||
|
||||
FIXTURE_PORTS: List["MockPort"] = []
|
||||
|
||||
|
||||
def is_bluetooth_port(device: str) -> bool:
|
||||
lower = device.lower()
|
||||
@@ -65,6 +102,43 @@ def parse_location(port) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def load_ports_fixture(fixture_path: Path) -> List[MockPort]:
|
||||
if not fixture_path.exists():
|
||||
return []
|
||||
ports: List[MockPort] = []
|
||||
with fixture_path.open(encoding="utf-8") as handle:
|
||||
reader = csv.reader(handle)
|
||||
for row in reader:
|
||||
if not row or row[0].strip().startswith("#"):
|
||||
continue
|
||||
device = row[0].strip()
|
||||
if not device:
|
||||
continue
|
||||
location = row[1].strip() if len(row) > 1 else ""
|
||||
description = row[2].strip() if len(row) > 2 else "mock"
|
||||
vidpid = row[3].strip() if len(row) > 3 else ""
|
||||
vid = pid = 0
|
||||
if vidpid and ":" in vidpid:
|
||||
parts = vidpid.split(":", 1)
|
||||
try:
|
||||
vid = int(parts[0], 16)
|
||||
pid = int(parts[1], 16)
|
||||
except Exception:
|
||||
vid = pid = 0
|
||||
ports.append(
|
||||
MockPort(
|
||||
device=device,
|
||||
location=location,
|
||||
description=description,
|
||||
manufacturer="mock",
|
||||
product="mock",
|
||||
vid=vid,
|
||||
pid=pid,
|
||||
)
|
||||
)
|
||||
return ports
|
||||
|
||||
|
||||
def load_map_file(path: Path) -> Dict[str, Dict[str, str]]:
|
||||
data = {"location": {}, "vidpid": {}}
|
||||
if not path.exists():
|
||||
@@ -266,6 +340,10 @@ def choose_interactive(candidates: List[dict], role: str) -> Optional[str]:
|
||||
|
||||
def gather_ports(wait_port: int) -> List:
|
||||
deadline = time.monotonic() + max(1, wait_port)
|
||||
if FIXTURE_PORTS:
|
||||
return FIXTURE_PORTS
|
||||
if list_ports is None:
|
||||
return []
|
||||
while True:
|
||||
ports = list(list_ports.comports())
|
||||
if ports:
|
||||
@@ -411,6 +489,8 @@ def main() -> int:
|
||||
parser.add_argument("--auto-ports", dest="auto_ports", action="store_true", default=True)
|
||||
parser.add_argument("--no-auto-ports", dest="auto_ports", action="store_false")
|
||||
parser.add_argument("--prefer-cu", action="store_true")
|
||||
parser.add_argument("--mock", action="store_true")
|
||||
parser.add_argument("--ports-fixture", default="")
|
||||
parser.add_argument("--interactive", action="store_true")
|
||||
parser.add_argument("--ports-resolve-json", default="")
|
||||
args = parser.parse_args()
|
||||
@@ -430,6 +510,20 @@ def main() -> int:
|
||||
args.port_esp8266 = env_esp8266
|
||||
env_override["esp8266"] = True
|
||||
|
||||
env_mock = os.environ.get("ZACUS_MOCK_PORTS", "0") == "1"
|
||||
fixture_arg = args.ports_fixture or os.environ.get("ZACUS_PORTS_FIXTURE", "")
|
||||
use_mock = args.mock or env_mock or bool(fixture_arg)
|
||||
mock_notes: List[str] = []
|
||||
if use_mock:
|
||||
fixture_path = Path(fixture_arg) if fixture_arg else DEFAULT_PORTS_FIXTURE
|
||||
if fixture_path.exists():
|
||||
FIXTURE_PORTS.clear()
|
||||
FIXTURE_PORTS.extend(load_ports_fixture(fixture_path))
|
||||
if FIXTURE_PORTS:
|
||||
mock_notes.append(f"mock fixture={fixture_path.name}")
|
||||
else:
|
||||
mock_notes.append(f"mock fixture missing: {fixture_path}")
|
||||
|
||||
ports_map = load_ports_map()
|
||||
|
||||
initial_ports = {"esp32": args.port_esp32, "esp8266": args.port_esp8266}
|
||||
@@ -458,6 +552,9 @@ def main() -> int:
|
||||
},
|
||||
"notes": [],
|
||||
}
|
||||
result["notes"].extend(mock_notes)
|
||||
if PY_SERIAL_ERROR and not use_mock:
|
||||
result["notes"].append(f"pyserial missing: {PY_SERIAL_ERROR}")
|
||||
|
||||
missing_roles = []
|
||||
if args.need_esp32 and not result["ports"]["esp32"]:
|
||||
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
FIXTURE="$ROOT/tools/test/fixtures/ports_list_macos.txt"
|
||||
ZACUS_CMD="$ROOT/tools/dev/zacus.sh"
|
||||
|
||||
run_help() {
|
||||
printf '== running help ==\n'
|
||||
ZACUS_MOCK_PORTS=1 ZACUS_PORTS_FIXTURE="$FIXTURE" "$ZACUS_CMD" help >/dev/null
|
||||
}
|
||||
|
||||
run_ports() {
|
||||
printf '== running ports (mock) ==\n'
|
||||
ZACUS_MOCK_PORTS=1 ZACUS_PORTS_FIXTURE="$FIXTURE" "$ZACUS_CMD" ports >/dev/null
|
||||
local json="$ROOT/artifacts/ports/latest_ports_resolve.json"
|
||||
if [[ ! -f "$json" ]]; then
|
||||
printf 'ports json missing: %s\n' "$json"
|
||||
exit 1
|
||||
fi
|
||||
python3 - "$json" <<'PY'
|
||||
import json, pathlib, sys
|
||||
path = pathlib.Path(sys.argv[1])
|
||||
data = json.loads(path.read_text(encoding='utf-8'))
|
||||
if 'status' not in data:
|
||||
raise SystemExit('status missing')
|
||||
print('ports status:', data['status'])
|
||||
PY
|
||||
}
|
||||
|
||||
run_codex_expect_fail() {
|
||||
printf '== running codex (expect exit 2) ==\n'
|
||||
set +e
|
||||
PATH="/bin:/usr/bin" ZACUS_MOCK_PORTS=1 ZACUS_PORTS_FIXTURE="$FIXTURE" "$ZACUS_CMD" codex --prompt "$ROOT/tools/dev/codex_prompts/zacus_hw_now.md"
|
||||
local code=$?
|
||||
set -e
|
||||
if [[ $code -ne 2 ]]; then
|
||||
printf 'expected codex exit 2, got %d\n' "$code"
|
||||
exit 1
|
||||
fi
|
||||
printf 'codex exit as expected\n'
|
||||
}
|
||||
|
||||
run_menu_non_tui() {
|
||||
printf '== running menu --no-tui ==\n'
|
||||
ZACUS_MOCK_PORTS=1 ZACUS_PORTS_FIXTURE="$FIXTURE" "$ZACUS_CMD" menu --no-tui >/dev/null
|
||||
}
|
||||
|
||||
run_help
|
||||
run_ports
|
||||
run_codex_expect_fail
|
||||
run_menu_non_tui
|
||||
Reference in New Issue
Block a user