Harden Zacus CLI for mock workflows
This commit is contained in:
Vendored
+17
-19
@@ -5,7 +5,8 @@
|
|||||||
"label": "Zacus: Firmware · Bootstrap venv",
|
"label": "Zacus: Firmware · Bootstrap venv",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "bash",
|
"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": [],
|
"problemMatcher": [],
|
||||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||||
},
|
},
|
||||||
@@ -15,8 +16,9 @@
|
|||||||
"command": "bash",
|
"command": "bash",
|
||||||
"args": [
|
"args": [
|
||||||
"-lc",
|
"-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": [],
|
"problemMatcher": [],
|
||||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||||
},
|
},
|
||||||
@@ -25,7 +27,7 @@
|
|||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "bash",
|
"command": "bash",
|
||||||
"args": ["-lc", "./tools/dev/zacus.sh rc"],
|
"args": ["-lc", "./tools/dev/zacus.sh rc"],
|
||||||
"options": { "cwd": "${workspaceFolder}" },
|
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||||
"problemMatcher": [],
|
"problemMatcher": [],
|
||||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||||
},
|
},
|
||||||
@@ -37,18 +39,7 @@
|
|||||||
"-lc",
|
"-lc",
|
||||||
"./tools/dev/zacus.sh codex --prompt tools/dev/codex_prompts/zacus_overhaul_one_shot.md"
|
"./tools/dev/zacus.sh codex --prompt tools/dev/codex_prompts/zacus_overhaul_one_shot.md"
|
||||||
],
|
],
|
||||||
"options": { "cwd": "${workspaceFolder}" },
|
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||||
"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"
|
|
||||||
],
|
|
||||||
"problemMatcher": [],
|
"problemMatcher": [],
|
||||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||||
},
|
},
|
||||||
@@ -58,8 +49,9 @@
|
|||||||
"command": "bash",
|
"command": "bash",
|
||||||
"args": [
|
"args": [
|
||||||
"-lc",
|
"-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": [],
|
"problemMatcher": [],
|
||||||
"presentation": { "panel": "dedicated", "group": "zacus-watch", "reveal": "always" }
|
"presentation": { "panel": "dedicated", "group": "zacus-watch", "reveal": "always" }
|
||||||
},
|
},
|
||||||
@@ -69,8 +61,9 @@
|
|||||||
"command": "bash",
|
"command": "bash",
|
||||||
"args": [
|
"args": [
|
||||||
"-lc",
|
"-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": [],
|
"problemMatcher": [],
|
||||||
"presentation": { "panel": "dedicated", "group": "zacus-watch", "reveal": "always" }
|
"presentation": { "panel": "dedicated", "group": "zacus-watch", "reveal": "always" }
|
||||||
},
|
},
|
||||||
@@ -80,8 +73,9 @@
|
|||||||
"command": "bash",
|
"command": "bash",
|
||||||
"args": [
|
"args": [
|
||||||
"-lc",
|
"-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": [],
|
"problemMatcher": [],
|
||||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
||||||
},
|
},
|
||||||
@@ -90,8 +84,12 @@
|
|||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "bash",
|
"command": "bash",
|
||||||
"args": ["-lc", "echo \"Cockpit started (stop tasks to close watchers).\""],
|
"args": ["-lc", "echo \"Cockpit started (stop tasks to close watchers).\""],
|
||||||
|
"options": { "cwd": "${workspaceFolder}/hardware/firmware" },
|
||||||
"dependsOrder": "parallel",
|
"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": [],
|
"problemMatcher": [],
|
||||||
"presentation": { "panel": "dedicated", "group": "zacus", "reveal": "always" }
|
"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
|
- `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é)
|
- `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.
|
- `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
|
## État du projet
|
||||||
- **Kit MJ** : complet (script, plan, antic-chaos, solution, checklists, export PDF).
|
- **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
|
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:
|
Dépendances optionnelles:
|
||||||
- `pip install pyyaml` pour `run_content_checks.sh`
|
- `pip install pyyaml` pour `run_content_checks.sh`
|
||||||
- `pip install pyserial` pour suites USB, UI Link sim et console série
|
- `pip install pyserial` pour suites USB, UI Link sim et console série
|
||||||
|
|||||||
Vendored
+9
-11
@@ -5,10 +5,7 @@
|
|||||||
"label": "Zacus: RC Live",
|
"label": "Zacus: RC Live",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "bash",
|
"command": "bash",
|
||||||
"args": [
|
"args": ["-lc", "set -euo pipefail; ./tools/dev/zacus.sh rc"],
|
||||||
"-lc",
|
|
||||||
"set -euo pipefail; ROOT=$(git rev-parse --show-toplevel); \"$ROOT/tools/dev/zacus.sh\" rc"
|
|
||||||
],
|
|
||||||
"options": { "cwd": "${workspaceFolder}" },
|
"options": { "cwd": "${workspaceFolder}" },
|
||||||
"presentation": { "panel": "dedicated", "reveal": "always" },
|
"presentation": { "panel": "dedicated", "reveal": "always" },
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
@@ -19,7 +16,7 @@
|
|||||||
"command": "bash",
|
"command": "bash",
|
||||||
"args": [
|
"args": [
|
||||||
"-lc",
|
"-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}" },
|
"options": { "cwd": "${workspaceFolder}" },
|
||||||
"presentation": { "panel": "dedicated", "reveal": "always" },
|
"presentation": { "panel": "dedicated", "reveal": "always" },
|
||||||
@@ -29,10 +26,7 @@
|
|||||||
"label": "Zacus: RC Live + AutoFix",
|
"label": "Zacus: RC Live + AutoFix",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "bash",
|
"command": "bash",
|
||||||
"args": [
|
"args": ["-lc", "set -euo pipefail; ./tools/dev/zacus.sh rc-autofix"],
|
||||||
"-lc",
|
|
||||||
"set -euo pipefail; ROOT=$(git rev-parse --show-toplevel); \"$ROOT/tools/dev/zacus.sh\" rc-autofix"
|
|
||||||
],
|
|
||||||
"options": { "cwd": "${workspaceFolder}" },
|
"options": { "cwd": "${workspaceFolder}" },
|
||||||
"presentation": { "panel": "dedicated", "reveal": "always" },
|
"presentation": { "panel": "dedicated", "reveal": "always" },
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
@@ -43,10 +37,14 @@
|
|||||||
"command": "bash",
|
"command": "bash",
|
||||||
"args": [
|
"args": [
|
||||||
"-lc",
|
"-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}" },
|
"options": { "cwd": "${workspaceFolder}" },
|
||||||
"presentation": { "panel": "dedicated", "reveal": "always", "showReuseMessage": false },
|
"presentation": {
|
||||||
|
"panel": "dedicated",
|
||||||
|
"reveal": "always",
|
||||||
|
"showReuseMessage": false
|
||||||
|
},
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
||||||
|
# --- Codex Prompt Menu TUI ---
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
REPO_ROOT="$(git -C "$SCRIPT_DIR/.." rev-parse --show-toplevel)"
|
REPO_ROOT="$(git -C "$SCRIPT_DIR/.." rev-parse --show-toplevel)"
|
||||||
FW_ROOT="$REPO_ROOT/hardware/firmware"
|
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"
|
ARTIFACTS_DIR="$FW_ROOT/artifacts/rc_live"
|
||||||
LAST_MESSAGE_FILE="$ARTIFACTS_DIR/_codex_last_message.md"
|
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() {
|
collect_prompts() {
|
||||||
local glob
|
local glob
|
||||||
prompt_files=()
|
prompt_files=()
|
||||||
@@ -17,6 +28,8 @@ collect_prompts() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Menu interactif pour choisir un prompt (TUI si possible)
|
||||||
main_menu() {
|
main_menu() {
|
||||||
collect_prompts
|
collect_prompts
|
||||||
if [[ ${#prompt_files[@]} -eq 0 ]]; then
|
if [[ ${#prompt_files[@]} -eq 0 ]]; then
|
||||||
@@ -24,25 +37,43 @@ main_menu() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
while true; do
|
while true; do
|
||||||
echo
|
local choice
|
||||||
echo "Available prompts:"
|
if [[ -n "$TUI_CMD" ]]; then
|
||||||
for idx in "${!prompt_files[@]}"; do
|
local menu_args=()
|
||||||
printf "%2d) %s\n" $((idx + 1)) "$(basename "${prompt_files[idx]}")"
|
for idx in "${!prompt_files[@]}"; do
|
||||||
done
|
menu_args+=("$idx" "$(basename "${prompt_files[idx]}")")
|
||||||
read -rp "Choice (q to quit): " choice
|
done
|
||||||
if [[ "$choice" =~ ^[Qq]$ ]]; then
|
choice=$( \
|
||||||
exit 0
|
$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
|
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
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
||||||
|
# --- Zacus.sh TUI ---
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
ARTIFACT_ROOT="$REPO_ROOT/artifacts/rc_live"
|
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"
|
RC_RUNNER="$REPO_ROOT/tools/dev/run_matrix_and_smoke.sh"
|
||||||
PROMPT_DIR="$SCRIPT_DIR/codex_prompts"
|
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() {
|
require_cmd() {
|
||||||
if ! command -v "$1" >/dev/null 2>&1; then
|
if ! command -v "$1" >/dev/null 2>&1; then
|
||||||
echo "[zacus] missing command: $1" >&2
|
echo "[zacus] missing command: $1" >&2
|
||||||
@@ -193,10 +204,36 @@ HELP
|
|||||||
|
|
||||||
mkdir -p "$ARTIFACT_ROOT"
|
mkdir -p "$ARTIFACT_ROOT"
|
||||||
|
|
||||||
|
|
||||||
|
# Menu interactif si aucun argument fourni
|
||||||
command=${1:-}
|
command=${1:-}
|
||||||
if [[ -z "$command" ]]; then
|
if [[ -z "$command" ]]; then
|
||||||
usage
|
if [[ -n "$TUI_CMD" ]]; then
|
||||||
exit 1
|
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
|
fi
|
||||||
|
|
||||||
case "$command" in
|
case "$command" in
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -euo pipefail
|
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)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
REPO_ROOT="$(git -C "$SCRIPT_DIR/../.." rev-parse --show-toplevel)"
|
REPO_ROOT="$(git -C "$SCRIPT_DIR/../.." rev-parse --show-toplevel)"
|
||||||
FW_ROOT="$REPO_ROOT/hardware/firmware"
|
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"
|
PROMPT_DIR="$FW_ROOT/tools/dev/codex_prompts"
|
||||||
RC_PROMPT="$PROMPT_DIR/rc_live_fail.prompt.md"
|
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() {
|
run_bootstrap() {
|
||||||
(cd "$REPO_ROOT" && ./tools/dev/bootstrap_local.sh)
|
"$FW_ROOT/tools/dev/bootstrap_local.sh"
|
||||||
}
|
}
|
||||||
|
|
||||||
run_build_all() {
|
run_build_all() {
|
||||||
(cd "$FW_ROOT" && ./build_all.sh)
|
"$FW_ROOT/build_all.sh"
|
||||||
}
|
}
|
||||||
|
|
||||||
run_rc_live() {
|
run_rc_live() {
|
||||||
local artifacts=""
|
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)"
|
artifacts="$(latest_artifacts)"
|
||||||
if [[ -n "$artifacts" && -f "$RC_PROMPT" ]]; then
|
if [[ -n "$artifacts" && -f "$RC_PROMPT" ]]; then
|
||||||
ARTIFACT_PATH="$artifacts" codex exec - < "$RC_PROMPT"
|
ARTIFACT_PATH="$artifacts" codex exec - < "$RC_PROMPT"
|
||||||
@@ -46,34 +74,212 @@ latest_artifacts() {
|
|||||||
ls -1d "$ARTIFACTS_ROOT"/*/ 2>/dev/null | sort | tail -n1
|
ls -1d "$ARTIFACTS_ROOT"/*/ 2>/dev/null | sort | tail -n1
|
||||||
}
|
}
|
||||||
|
|
||||||
menu() {
|
|
||||||
cat <<'EOF'
|
# Affichage du menu (fzf > dialog > texte coloré)
|
||||||
Firmware cockpit
|
show_menu() {
|
||||||
1) bootstrap (tools/dev/bootstrap_local.sh)
|
local options=(
|
||||||
2) build all firmware (hardware/firmware/build_all.sh)
|
"1:RC live gate (ZACUS_REQUIRE_HW unset)"
|
||||||
3) rc live gate (ZACUS_REQUIRE_HW unset)
|
"2:RC live gate (ZACUS_REQUIRE_HW=1)"
|
||||||
4) rc live gate (ZACUS_REQUIRE_HW=1)
|
"3:Build all firmware (hardware/firmware/build_all.sh)"
|
||||||
5) watch serial ports
|
"4:Bootstrap (outils/dev/bootstrap_local.sh)"
|
||||||
6) run codex prompt menu
|
"5:Watch serial ports"
|
||||||
0) exit
|
"6:Run codex prompt menu"
|
||||||
EOF
|
"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() {
|
run_codex_prompts() {
|
||||||
"$FW_ROOT/tools/dev/codex_prompt_menu.sh"
|
"$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
|
while true; do
|
||||||
menu
|
echo "[DEBUG] Affichage du menu..." >&2
|
||||||
read -rp "Choice: " choice
|
choice=$(show_menu)
|
||||||
|
echo "[DEBUG] Choix utilisateur: '$choice'" >&2
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1) run_bootstrap ;;
|
1) ZACUS_REQUIRE_HW=0 run_rc_live ;;
|
||||||
2) run_build_all ;;
|
2) ZACUS_REQUIRE_HW=1 run_rc_live ;;
|
||||||
3) ZACUS_REQUIRE_HW=0 run_rc_live ;;
|
3) run_build_all ;;
|
||||||
4) ZACUS_REQUIRE_HW=1 run_rc_live ;;
|
4) run_bootstrap ;;
|
||||||
5) ports_watch ;;
|
5) ports_watch ;;
|
||||||
6) run_codex_prompts ;;
|
6) run_codex_prompts ;;
|
||||||
0) exit 0 ;;
|
7) afficher_logs_menu ;;
|
||||||
*) echo "Unknown option: $choice" ;;
|
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
|
esac
|
||||||
done
|
done
|
||||||
|
|||||||
+130
-38
@@ -2,7 +2,7 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
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"
|
FW_ROOT="$REPO_ROOT/hardware/firmware"
|
||||||
RC_RUNNER="$FW_ROOT/tools/dev/run_matrix_and_smoke.sh"
|
RC_RUNNER="$FW_ROOT/tools/dev/run_matrix_and_smoke.sh"
|
||||||
SMOKE_SCRIPT="$FW_ROOT/tools/dev/serial_smoke.py"
|
SMOKE_SCRIPT="$FW_ROOT/tools/dev/serial_smoke.py"
|
||||||
@@ -11,6 +11,7 @@ ARTIFACT_ROOT="$REPO_ROOT/artifacts"
|
|||||||
PORTS_ARTIFACT_ROOT="$ARTIFACT_ROOT/ports"
|
PORTS_ARTIFACT_ROOT="$ARTIFACT_ROOT/ports"
|
||||||
LATEST_PORTS_JSON="$PORTS_ARTIFACT_ROOT/latest_ports_resolve.json"
|
LATEST_PORTS_JSON="$PORTS_ARTIFACT_ROOT/latest_ports_resolve.json"
|
||||||
CODEX_ARTIFACT_ROOT="$ARTIFACT_ROOT/codex"
|
CODEX_ARTIFACT_ROOT="$ARTIFACT_ROOT/codex"
|
||||||
|
DEFAULT_PORTS_FIXTURE="$REPO_ROOT/tools/test/fixtures/ports_list_macos.txt"
|
||||||
LAST_RESOLVED_JSON=""
|
LAST_RESOLVED_JSON=""
|
||||||
|
|
||||||
info() {
|
info() {
|
||||||
@@ -22,15 +23,20 @@ die() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
die_codex() {
|
||||||
|
printf '[zacus] ERROR: %s\n' "$*" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'USAGE'
|
cat <<'USAGE'
|
||||||
Usage: zacus.sh <command> [args]
|
Usage: zacus.sh <command> [args]
|
||||||
Commands:
|
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)
|
smoke Run the serial smoke helper (pass --role ... args)
|
||||||
ports Resolve ports once and show summary
|
ports Resolve ports once and show summary
|
||||||
codex Invoke codex exec with a prompt (see --help)
|
codex Invoke Codex CLI with a prompt
|
||||||
menu Show the zacus cockpit menu
|
menu Show the Zacus cockpit menu
|
||||||
help Show this usage
|
help Show this usage
|
||||||
USAGE
|
USAGE
|
||||||
}
|
}
|
||||||
@@ -41,24 +47,38 @@ print_ports_summary() {
|
|||||||
printf '[ports] no resolved data yet (run "zacus.sh ports" to resolve)\n'
|
printf '[ports] no resolved data yet (run "zacus.sh ports" to resolve)\n'
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
python3 - <<'PY'
|
python3 - "$json_file" <<'PY'
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
import pathlib
|
import pathlib
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
path = pathlib.Path(sys.argv[1])
|
path = pathlib.Path(sys.argv[1])
|
||||||
data = json.loads(path.read_text(encoding='utf-8'))
|
try:
|
||||||
ports = data.get('ports', {})
|
data = json.loads(path.read_text(encoding='utf-8'))
|
||||||
reasons = data.get('reasons', {})
|
ports = data.get('ports', {})
|
||||||
notes = data.get('notes', [])
|
reasons = data.get('reasons', {})
|
||||||
labels = [('esp32', 'esp32_audio'), ('esp8266', 'esp8266_oled')]
|
notes = data.get('notes', [])
|
||||||
print(f'[ports] summary ({path.name}):')
|
labels = [('esp32', 'esp32_audio'), ('esp8266', 'esp8266_oled')]
|
||||||
for role, label in labels:
|
timestamp = path.parent.name
|
||||||
port = ports.get(role) or 'n/a'
|
age = 'unknown'
|
||||||
reason = reasons.get(role) or 'unset'
|
try:
|
||||||
print(f' - {label}: {port} (reason={reason})')
|
dt = datetime.datetime.strptime(timestamp, '%Y%m%d-%H%M%S')
|
||||||
print(' - rp2040_tft: not auto-resolved (manual selection)')
|
dt = dt.replace(tzinfo=datetime.timezone.utc)
|
||||||
print('[ports] notes:', ' | '.join(notes) if notes else 'none')
|
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
|
PY
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,24 +89,64 @@ resolve_ports_once() {
|
|||||||
local dir="$PORTS_ARTIFACT_ROOT/$timestamp"
|
local dir="$PORTS_ARTIFACT_ROOT/$timestamp"
|
||||||
mkdir -p "$dir"
|
mkdir -p "$dir"
|
||||||
local json="$dir/ports_resolve.json"
|
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)"
|
info "resolving ports (output -> $json)"
|
||||||
set +e
|
set +e
|
||||||
python3 "$REPO_ROOT/tools/test/resolve_ports.py" \
|
"${resolver_args[@]}"
|
||||||
--need-esp32 --need-esp8266 --ports-resolve-json "$json" --wait-port 3
|
|
||||||
local rc=$?
|
local rc=$?
|
||||||
set -e
|
set -e
|
||||||
if [[ -f "$json" ]]; then
|
if [[ -f "$json" ]]; then
|
||||||
cp "$json" "$LATEST_PORTS_JSON" 2>/dev/null || true
|
cp "$json" "$LATEST_PORTS_JSON" 2>/dev/null || true
|
||||||
|
LAST_RESOLVED_JSON="$json"
|
||||||
fi
|
fi
|
||||||
LAST_RESOLVED_JSON="$json"
|
|
||||||
return $rc
|
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() {
|
cmd_ports() {
|
||||||
resolve_ports_once || true
|
resolve_ports_once || true
|
||||||
if [[ -n "${LAST_RESOLVED_JSON:-}" ]]; then
|
local json_file=""
|
||||||
print_ports_summary "$LAST_RESOLVED_JSON" || true
|
if [[ -n "${LAST_RESOLVED_JSON}" && -f "$LAST_RESOLVED_JSON" ]]; then
|
||||||
printf '[ports] data file: %s\n' "$LAST_RESOLVED_JSON"
|
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
|
else
|
||||||
printf '[ports] resolver did not produce a JSON file\n'
|
printf '[ports] resolver did not produce a JSON file\n'
|
||||||
fi
|
fi
|
||||||
@@ -107,10 +167,10 @@ cmd_smoke() {
|
|||||||
|
|
||||||
ensure_codex_ready() {
|
ensure_codex_ready() {
|
||||||
if ! command -v codex >/dev/null 2>&1; then
|
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
|
fi
|
||||||
if ! codex login status >/dev/null 2>&1; then
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +178,6 @@ cmd_codex() {
|
|||||||
local prompt_file=""
|
local prompt_file=""
|
||||||
local sandbox="workspace-write"
|
local sandbox="workspace-write"
|
||||||
local auto_mode="0"
|
local auto_mode="0"
|
||||||
local custom_sandbox=""
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--prompt)
|
--prompt)
|
||||||
@@ -154,16 +213,17 @@ HELP
|
|||||||
fi
|
fi
|
||||||
ensure_codex_ready
|
ensure_codex_ready
|
||||||
resolve_ports_once || true
|
resolve_ports_once || true
|
||||||
if [[ -n "${LAST_RESOLVED_JSON:-}" ]]; then
|
local ports_json="${LAST_RESOLVED_JSON:-$LATEST_PORTS_JSON}"
|
||||||
print_ports_summary "$LAST_RESOLVED_JSON" || true
|
if [[ -n "$ports_json" && -f "$ports_json" ]]; then
|
||||||
|
print_ports_summary "$ports_json" || true
|
||||||
fi
|
fi
|
||||||
mkdir -p "$CODEX_ARTIFACT_ROOT"
|
mkdir -p "$CODEX_ARTIFACT_ROOT"
|
||||||
local ts
|
local ts
|
||||||
ts=$(date -u +%Y%m%d-%H%M%S)
|
ts=$(date -u +%Y%m%d-%H%M%S)
|
||||||
local art_dir="$CODEX_ARTIFACT_ROOT/$ts"
|
local art_dir="$CODEX_ARTIFACT_ROOT/$ts"
|
||||||
mkdir -p "$art_dir"
|
mkdir -p "$art_dir"
|
||||||
if [[ -f "$LAST_RESOLVED_JSON" ]]; then
|
if [[ -n "$ports_json" && -f "$ports_json" ]]; then
|
||||||
cp "$LAST_RESOLVED_JSON" "$art_dir/ports_resolve.json" 2>/dev/null || true
|
cp "$ports_json" "$art_dir/ports_resolve.json" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
printf 'prompt=%s\nsandbox=%s\nauto=%s\n' "$prompt_file" "$sandbox" "$auto_mode" > "$art_dir/metadata.txt"
|
printf 'prompt=%s\nsandbox=%s\nauto=%s\n' "$prompt_file" "$sandbox" "$auto_mode" > "$art_dir/metadata.txt"
|
||||||
local exec_cmd=(codex exec --sandbox "$sandbox")
|
local exec_cmd=(codex exec --sandbox "$sandbox")
|
||||||
@@ -241,9 +301,29 @@ menu_run_codex() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd_menu() {
|
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
|
while true; do
|
||||||
printf '\n=== Zacus cockpit ===\n'
|
printf '\n=== Zacus cockpit ===\n'
|
||||||
print_ports_summary "${LATEST_PORTS_JSON}" || true
|
show_ports_context || true
|
||||||
printf '[menu] options:\n'
|
printf '[menu] options:\n'
|
||||||
printf ' 1) RC live\n'
|
printf ' 1) RC live\n'
|
||||||
printf ' 2) Serial smoke\n'
|
printf ' 2) Serial smoke\n'
|
||||||
@@ -252,12 +332,24 @@ cmd_menu() {
|
|||||||
printf ' 0) Exit\n'
|
printf ' 0) Exit\n'
|
||||||
read -rp 'Choice: ' choice
|
read -rp 'Choice: ' choice
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1) cmd_rc ;;
|
1)
|
||||||
2) cmd_smoke ;;
|
cmd_rc
|
||||||
3) menu_run_codex ;;
|
;;
|
||||||
4) cmd_ports ;;
|
2)
|
||||||
0) return 0 ;;
|
cmd_smoke
|
||||||
*) printf 'unknown choice\n' ;;
|
;;
|
||||||
|
3)
|
||||||
|
menu_run_codex
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
cmd_ports
|
||||||
|
;;
|
||||||
|
0)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'unknown choice\n'
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
@@ -278,7 +370,7 @@ case "$command" in
|
|||||||
cmd_codex "$@"
|
cmd_codex "$@"
|
||||||
;;
|
;;
|
||||||
menu)
|
menu)
|
||||||
cmd_menu
|
cmd_menu "$@"
|
||||||
;;
|
;;
|
||||||
help|-h|--help)
|
help|-h|--help)
|
||||||
usage
|
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 sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import csv
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
PY_SERIAL_ERROR = ""
|
||||||
try:
|
try:
|
||||||
import serial
|
import serial
|
||||||
from serial.tools import list_ports
|
from serial.tools import list_ports
|
||||||
except ImportError:
|
except ImportError as exc:
|
||||||
print(json.dumps({"status": "fail", "notes": ["pyserial missing: pip install pyserial"]}))
|
serial = None
|
||||||
raise SystemExit(2)
|
list_ports = None
|
||||||
|
PY_SERIAL_ERROR = str(exc)
|
||||||
|
|
||||||
|
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
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_BAUDS = (115200, 19200)
|
||||||
FINGERPRINT_TIMEOUT = 2.0
|
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:
|
def is_bluetooth_port(device: str) -> bool:
|
||||||
lower = device.lower()
|
lower = device.lower()
|
||||||
@@ -65,6 +102,43 @@ def parse_location(port) -> str:
|
|||||||
return ""
|
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]]:
|
def load_map_file(path: Path) -> Dict[str, Dict[str, str]]:
|
||||||
data = {"location": {}, "vidpid": {}}
|
data = {"location": {}, "vidpid": {}}
|
||||||
if not path.exists():
|
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:
|
def gather_ports(wait_port: int) -> List:
|
||||||
deadline = time.monotonic() + max(1, wait_port)
|
deadline = time.monotonic() + max(1, wait_port)
|
||||||
|
if FIXTURE_PORTS:
|
||||||
|
return FIXTURE_PORTS
|
||||||
|
if list_ports is None:
|
||||||
|
return []
|
||||||
while True:
|
while True:
|
||||||
ports = list(list_ports.comports())
|
ports = list(list_ports.comports())
|
||||||
if ports:
|
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("--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("--no-auto-ports", dest="auto_ports", action="store_false")
|
||||||
parser.add_argument("--prefer-cu", action="store_true")
|
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("--interactive", action="store_true")
|
||||||
parser.add_argument("--ports-resolve-json", default="")
|
parser.add_argument("--ports-resolve-json", default="")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -430,6 +510,20 @@ def main() -> int:
|
|||||||
args.port_esp8266 = env_esp8266
|
args.port_esp8266 = env_esp8266
|
||||||
env_override["esp8266"] = True
|
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()
|
ports_map = load_ports_map()
|
||||||
|
|
||||||
initial_ports = {"esp32": args.port_esp32, "esp8266": args.port_esp8266}
|
initial_ports = {"esp32": args.port_esp32, "esp8266": args.port_esp8266}
|
||||||
@@ -458,6 +552,9 @@ def main() -> int:
|
|||||||
},
|
},
|
||||||
"notes": [],
|
"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 = []
|
missing_roles = []
|
||||||
if args.need_esp32 and not result["ports"]["esp32"]:
|
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