# AM62X 開發板連線配置與參數管理 Implementation Plan

## Overview

實現 ATK-DLAM62xB (AM62X) 開發板的三個核心功能：
1. **連線配置化**：將硬編碼的 SSH/SCP 連線資訊移至 config.toml
2. **參數傳遞**：支援傳遞執行參數給部署後的二進位程式（如 `./led on`）
3. **Scenarios 系統**：支援預定義參數組合的場景管理

這些功能將使 FWAuto 支援無人工介入的自動化部署與測試流程。

## Current State Analysis

### 現況發現

**連線配置**：
- SSH/SCP 連線參數硬編碼在 `templates/sdk_am62x_linux/flash/flash.sh:10-14`
- 參數包括：BOARD_IP, BOARD_USER, DEPLOY_PATH, SSH_OPTS
- 無法在 config.toml 配置，不支援多開發板管理

**參數傳遞**：
- State 已定義 `command_args` 欄位 (state.py:58-59)
- Log 命令完整實現了參數傳遞模式 (cli.py:175-222)
- Flash 命令無參數接收，存在完整鏈路斷層：
  - ❌ CLI 無 `--binary-args` 選項
  - ❌ Flash node 未讀取 command_args
  - ❌ Utils 無 env_vars 支援
  - ❌ Makefile 無參數轉發
  - ❌ Shell 硬編碼執行 `./$binary`（無參數）

**Scenarios 系統**：
- 完全不存在
- 無 scenario 配置結構
- 無 scenario 管理命令

### 關鍵約束

1. **KISS 原則**：保持簡單，避免過度設計
2. **向後相容**：所有新功能都有合理的預設值
3. **配置繼承**：遵循 Project > User > Template > 預設值的優先級
4. **Maketool 抽象**：Python 只編排流程，toolchain 細節在 Makefile/Shell

## Desired End State

完成後使用者可以：

```bash
# Phase 1: 連線配置化
fwauto init  # 互動式配置開發板連線資訊
# → ~/.fwauto/config.toml 或 .fwauto/config.toml 包含 deployment 配置

# Phase 2: 參數傳遞
fwauto flash --binary-args "--mode test"
# → 遠端執行: ./app --mode test

# Phase 3: Scenarios
fwauto flash --scenario led-on
# → 自動使用預定義的參數組合
fwauto scenarios list
# → 列出所有可用的 scenarios
```

### 驗證方式

1. **連線配置**：修改 config.toml 中的 IP，flash 命令使用新 IP
2. **參數傳遞**：`--binary-args "arg1"` 的參數成功傳遞給遠端二進位
3. **Scenarios**：`--scenario test` 載入預定義參數並執行

## What We're NOT Doing

- ❌ 不處理 SSH 密碼或金鑰管理（當前開發板設定為無密碼登入，保持現狀）
- ❌ 不實現 AI 參數推薦（Phase 4 未來功能）
- ❌ 不實現複雜的參數驗證（如類型檢查、範圍驗證）
- ❌ 不實現 scenario 繼承或模板功能
- ❌ 不實現開發板自動發現
- ❌ 不修改 STM32 模板（本計劃專注 AM62X）

## Implementation Approach

採用漸進式實施策略，每個 Phase 獨立可測試：

```mermaid
graph LR
    A[Phase 1<br/>連線配置化] --> B[Phase 2<br/>參數傳遞]
    B --> C[Phase 3<br/>Scenarios]

    style A fill:#e3f2fd
    style B fill:#fff9c4
    style C fill:#c8e6c9
```

**Phase 1** 為後續奠定配置基礎，**Phase 2** 建立參數傳遞框架，**Phase 3** 在此基礎上實現高階功能。

---

## Phase 1: 連線配置化

### Overview

將 flash.sh 中硬編碼的連線參數移至 config.toml，支援 User 和 Project 兩層配置，並在 `fwauto init` 時互動式收集。

### 架構圖

```mermaid
graph TD
    A[User Config<br/>~/.fwauto/config.toml] -->|繼承| B[Project Config<br/>.fwauto/config.toml]
    B -->|讀取| C[ProjectConfig.deployment_config]
    C -->|環境變數| D[flash.sh]
    D -->|SSH/SCP| E[ATK-AM62x]

    style A fill:#fff9c4
    style B fill:#c8e6c9
    style C fill:#e3f2fd
    style D fill:#ffe1e1
    style E fill:#f0e1ff
```

### Changes Required

#### 1. UserConfig 擴展

**File**: `src/fwauto/user_config.py`

**Changes**: 新增 deployment config 的 getter/setter

```python
def get_deployment_config(self, sdk_type: str) -> dict[str, Any] | None:
    """取得特定 SDK 的 deployment 設定"""
    return self.data.get("deployment", {}).get(sdk_type)

def set_deployment_config(self, sdk_type: str, deployment_data: dict[str, Any]) -> None:
    """設定 deployment 資訊並儲存"""
    if "deployment" not in self.data:
        self.data["deployment"] = {}
    self.data["deployment"][sdk_type] = deployment_data
    self._save()
```

#### 2. ProjectConfig 擴展

**File**: `src/fwauto/config.py`

**Changes**: 新增 deployment_config 屬性（支援繼承）

```python
@property
def deployment_config(self) -> dict[str, Any]:
    """
    Deployment 配置優先從 Project Config 讀取，否則從 User Config 繼承

    Returns:
        包含 board_ip, board_user, deploy_path, ssh_options 的字典
        若無配置則返回空字典
    """
    sdk_type = self.sdk_type
    if not sdk_type:
        return {}

    # 1. 優先使用 Project Scope
    project_deployment = self.data.get("deployment", {}).get(sdk_type, {})
    if project_deployment:
        return project_deployment

    # 2. 從 User Scope 繼承
    user_deployment = self.user_config.get_deployment_config(sdk_type)
    return user_deployment if user_deployment else {}

@property
def board_ip(self) -> str:
    """開發板 IP 地址"""
    return self.deployment_config.get("board_ip", "192.168.50.170")

@property
def board_user(self) -> str:
    """SSH 登入使用者"""
    return self.deployment_config.get("board_user", "root")

@property
def deploy_path(self) -> str:
    """遠端部署目錄"""
    return self.deployment_config.get("deploy_path", "/home/root")

@property
def ssh_options(self) -> str:
    """SSH 選項"""
    default_opts = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5"
    return self.deployment_config.get("ssh_options", default_opts)
```

#### 3. Init 命令擴展

**File**: `src/fwauto/cli_commands/init.py`

**Changes**: 在初始化流程中新增 deployment 配置收集

在 Step 3（輸入裝置資訊）之後，新增 Step 3.5：

```python
# Step 3.5: 收集 deployment 配置（僅 AM62X）
if template_id == "am62x":
    console.print("\n[cyan]📡 Deployment Configuration[/cyan]")
    console.print("[dim]Configure SSH/SCP connection for AM62X board[/dim]\n")

    board_ip = Prompt.ask(
        "Board IP address",
        default="192.168.50.170"
    )

    board_user = Prompt.ask(
        "SSH user",
        default="root"
    )

    deploy_path = Prompt.ask(
        "Deploy path on board",
        default="/home/root"
    )

    # SSH options 使用預設值，不詢問（進階使用者可手動編輯）
    ssh_options = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5"

    deployment_data = {
        "board_ip": board_ip,
        "board_user": board_user,
        "deploy_path": deploy_path,
        "ssh_options": ssh_options,
    }

    # 儲存至 User Config（跨專案共用）
    user_config.set_deployment_config(sdk_type, deployment_data)

    console.print(f"[green]✓[/green] Deployment config saved to User Config")
```

#### 4. Flash Node 修改

**File**: `src/fwauto/nodes/flash.py`

**Changes**: 提取 deployment config 並傳遞給 Makefile

```python
def flash_node(state: FirmwareState) -> dict[str, Any]:
    # ... 現有代碼 ...

    # 載入 deployment 配置
    deployment_config = config.deployment_config

    # 構造環境變數
    env_vars = {
        "BOARD_IP": config.board_ip,
        "BOARD_USER": config.board_user,
        "DEPLOY_PATH": config.deploy_path,
        "SSH_OPTIONS": config.ssh_options,
    }

    logger.info(f"📡 Deployment target: {config.board_user}@{config.board_ip}:{config.deploy_path}")

    # 執行 make flash
    returncode, full_output = execute_make_command(
        makefile_path=makefile_path,
        target=target,
        project_root=project_root_path,
        env_vars=env_vars,  # 傳遞環境變數
        timeout=60,
    )

    # ... 現有代碼 ...
```

#### 5. Utils 層支援環境變數

**File**: `src/fwauto/utils.py`

**Changes**: execute_make_command 新增 env_vars 參數

```python
def execute_make_command(
    makefile_path: Path,
    target: str,
    project_root: Path,
    env_vars: dict[str, str] | None = None,  # 新增參數
    timeout: int = 60,
) -> tuple[int, str]:
    """
    Execute make command with environment variables support.

    Args:
        makefile_path: Path to Makefile
        target: Make target to execute
        project_root: Project root directory
        env_vars: Environment variables to pass to make (optional)
        timeout: Command timeout in seconds

    Returns:
        Tuple of (returncode, full_output)
    """
    import os

    # ... 現有代碼 ...

    # 設定環境變數
    if env_vars:
        process_env = os.environ.copy()
        process_env.update(env_vars)
        popen_kwargs["env"] = process_env
        logger.info(f"Environment variables: {list(env_vars.keys())}")

    # 執行命令
    process = subprocess.Popen(cmd, **popen_kwargs)

    # ... 現有代碼 ...
```

#### 6. Makefile 修改

**File**: `templates/sdk_am62x_linux/flash/Makefile`

**Changes**: 傳遞環境變數給 shell script

```makefile
# AM62X Flash Makefile
# Purpose: Entry point for flash operation, delegates to bash script

# 環境變數預設值（可被 Python 覆寫）
BOARD_IP ?= 192.168.50.170
BOARD_USER ?= root
DEPLOY_PATH ?= /home/root
SSH_OPTIONS ?= -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5

.PHONY: flash
flash:
	@BOARD_IP="$(BOARD_IP)" \
	 BOARD_USER="$(BOARD_USER)" \
	 DEPLOY_PATH="$(DEPLOY_PATH)" \
	 SSH_OPTIONS="$(SSH_OPTIONS)" \
	 bash flash.sh
```

#### 7. Shell Script 修改

**File**: `templates/sdk_am62x_linux/flash/flash.sh`

**Changes**: 從環境變數讀取配置（保留硬編碼作為 fallback）

```bash
#!/usr/bin/env bash
# AM62X Flash Script - SSH/SCP based deployment

set -euo pipefail

# 從環境變數讀取（fallback 到硬編碼預設值）
BOARD_IP="${BOARD_IP:-192.168.50.170}"
BOARD_USER="${BOARD_USER:-root}"
DEPLOY_PATH="${DEPLOY_PATH:-/home/root}"
BUILD_DIR="$(cd "$(dirname "$0")/../.." && pwd)/build"
SSH_OPTS="${SSH_OPTIONS:--o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5}"

# ... 其餘代碼保持不變 ...
```

### Success Criteria

#### Automated Verification:

- [ ] UserConfig 單元測試通過：`pytest tests/test_user_config.py -v`
- [ ] ProjectConfig 單元測試通過：`pytest tests/test_config.py -v`
- [ ] 環境變數傳遞測試通過：`pytest tests/test_utils.py::test_execute_make_command_with_env_vars -v`

#### Manual Verification:

- [ ] 執行 `fwauto init`，選擇 AM62X 模板，正確詢問 deployment 配置
- [ ] 檢查 `~/.fwauto/config.toml` 包含 `[deployment.am62x]` section
- [ ] 修改 config.toml 中的 BOARD_IP，執行 `fwauto flash`，確認使用新 IP
- [ ] 檢查 flash log 輸出包含正確的目標資訊（user@ip:path）

**Implementation Note**: 完成此 Phase 並通過所有自動化驗證後，暫停並進行手動測試確認，然後再進入 Phase 2。

---

## Phase 2: 基礎參數傳遞

### Overview

建立從 CLI 到遠端二進位的完整參數傳遞鏈路，支援 `fwauto flash --binary-args "arg1 arg2"`。

### 資料流程圖

```mermaid
sequenceDiagram
    participant CLI as CLI<br/>(cli.py)
    participant State as FirmwareState
    participant Node as Flash Node
    participant Utils as execute_make_command
    participant Make as Makefile
    participant Shell as flash.sh
    participant Board as ATK-AM62x

    CLI->>State: command_args = {"binary_args": "on"}
    State->>Node: state["command_args"]
    Node->>Node: 提取 binary_args
    Node->>Utils: env_vars = {"BINARY_ARGS": "on"}
    Utils->>Make: BINARY_ARGS="on" make flash
    Make->>Shell: BINARY_ARGS="on" bash flash.sh
    Shell->>Board: ssh ... "./led on"
    Board-->>Shell: 執行結果
    Shell-->>CLI: 返回狀態
```

### Changes Required

#### 1. CLI 新增參數選項

**File**: `src/fwauto/cli.py`

**Changes**: flash 和 run 命令新增 --binary-args

```python
@app.command()
def flash(
    binary_args: str = typer.Option(
        "",
        "--binary-args",
        "-ba",
        help="Arguments to pass to the binary (e.g., '--mode test' or 'on')",
    ),
):
    """
    📥 Flash firmware to device.

    Requires .fwauto/ configuration in current directory or parent directories.

    Examples:
        fwauto flash
        fwauto flash --binary-args "on"
        fwauto flash -ba "--mode test --timeout 5"
    """
    console.print("[cyan]📥 Flashing firmware (using .fwauto/ config)...[/cyan]")

    if binary_args:
        console.print(f"[cyan]📦 Binary args: {binary_args}[/cyan]")

    state: FirmwareState = {
        "user_prompt": "flash",
        "project_root": "",
        "project_initialized": False,
        "build_status": "pending",
        "build_errors": [],
        "build_log": "",
        "flash_status": "pending",
        "flash_errors": [],
        "flash_log": "",
        "iteration": 0,
        "max_retries": 3,
        "execution_mode": "flash",
        "command": "flash",
        "command_args": {"binary_args": binary_args},  # 新增
        "mode_metadata": None,
        "command_error": None,
        "log_file_path": None,
        "log_analysis_result": None,
        "firmware_path": None,
    }

    run_workflow(state)
```

同樣修改 `run()` 命令（行 138-172）。

#### 2. Flash Node 提取參數

**File**: `src/fwauto/nodes/flash.py`

**Changes**: 從 command_args 提取 binary_args 並加入 env_vars

```python
def flash_node(state: FirmwareState) -> dict[str, Any]:
    # ... 現有代碼 ...

    # 提取 binary arguments
    command_args = state.get("command_args") or {}
    binary_args = command_args.get("binary_args", "")

    # 基礎參數驗證（防止 shell 注入）
    if binary_args:
        # 檢查危險字符
        dangerous_chars = [';', '|', '&', '$', '`', '\n', '\r']
        if any(c in binary_args for c in dangerous_chars):
            error_msg = "Binary args contain dangerous shell metacharacters - rejected"
            logger.error(error_msg)
            return {
                **state,
                "flash_status": "error",
                "flash_errors": [error_msg],
                "flash_log": f"❌ {error_msg}",
            }

        logger.info(f"📦 Binary arguments: {binary_args}")

    # 構造環境變數（包含 Phase 1 的 deployment config）
    env_vars = {
        "BOARD_IP": config.board_ip,
        "BOARD_USER": config.board_user,
        "DEPLOY_PATH": config.deploy_path,
        "SSH_OPTIONS": config.ssh_options,
    }

    # 新增 binary_args
    if binary_args:
        env_vars["BINARY_ARGS"] = binary_args

    # 執行 make flash
    returncode, full_output = execute_make_command(
        makefile_path=makefile_path,
        target=target,
        project_root=project_root_path,
        env_vars=env_vars,
        timeout=60,
    )

    # ... 現有代碼 ...
```

#### 3. Makefile 新增參數轉發

**File**: `templates/sdk_am62x_linux/flash/Makefile`

**Changes**: 新增 BINARY_ARGS 變數

```makefile
# AM62X Flash Makefile

# Deployment 環境變數
BOARD_IP ?= 192.168.50.170
BOARD_USER ?= root
DEPLOY_PATH ?= /home/root
SSH_OPTIONS ?= -o StrictHostKeyChecking=no -o ConnectTimeout=5

# Binary 參數（Phase 2 新增）
BINARY_ARGS ?=

.PHONY: flash
flash:
	@BOARD_IP="$(BOARD_IP)" \
	 BOARD_USER="$(BOARD_USER)" \
	 DEPLOY_PATH="$(DEPLOY_PATH)" \
	 SSH_OPTIONS="$(SSH_OPTIONS)" \
	 BINARY_ARGS="$(BINARY_ARGS)" \
	 bash flash.sh
```

#### 4. Shell Script 使用參數

**File**: `templates/sdk_am62x_linux/flash/flash.sh`

**Changes**: execute_on_board 函數讀取並使用 BINARY_ARGS

```bash
execute_on_board() {
    local binary_name="$1"
    local binary_args="${BINARY_ARGS:-}"  # 從環境變數讀取，預設為空

    log_info "Executing $binary_name on board..."

    if [ -n "$binary_args" ]; then
        log_info "With arguments: $binary_args"
    fi

    echo "" >&2

    # 執行二進位（帶參數）
    if ! ssh $SSH_OPTS "$BOARD_USER@$BOARD_IP" \
        "cd $DEPLOY_PATH && chmod +x ./$binary_name && ./$binary_name $binary_args"; then
        log_error "Execution failed"
        exit 1
    fi

    log_success "Execution completed"
}
```

### Success Criteria

#### Automated Verification:

- [x] CLI 參數解析測試：`pytest tests/test_cli.py::test_flash_with_binary_args -v`
- [x] Flash node 參數提取測試：`pytest tests/nodes/test_flash_makefile.py -v`
- [x] 參數驗證測試（拒絕危險字符）：`pytest tests/nodes/test_flash_makefile.py::test_flash_node_rejects_dangerous_binary_args -v`

#### Manual Verification:

- [x] 執行 `fwauto flash --binary-args "test1"`，確認遠端執行 `./app test1`
- [x] 執行 `fwauto flash -ba "arg1 arg2"`，確認遠端執行 `./app arg1 arg2`
- [x] 執行 `fwauto flash`（無參數），確認遠端執行 `./app`（無額外參數）
- [x] 嘗試 `fwauto flash --binary-args "; rm -rf /"`，確認被拒絕並顯示錯誤（危險字符驗證機制位於 flash.py:80-92）

**Implementation Note**: 完成此 Phase 並通過所有自動化驗證後，暫停並進行手動測試確認，然後再進入 Phase 3。

---

## Phase 3: Scenarios 系統

### Overview

實現預定義參數組合的場景管理，支援 `fwauto flash --scenario led-on` 和 `fwauto scenarios` 子命令。

### 架構圖

```mermaid
graph TD
    A[Project Config<br/>.fwauto/config.toml] -->|scenarios| B[ProjectConfig]
    B -->|load_scenario| C[CLI --scenario]
    C -->|解析| D[binary_args]
    D -->|傳遞| E[Phase 2 流程]

    style A fill:#c8e6c9
    style B fill:#e3f2fd
    style C fill:#ffe1e1
    style D fill:#f0e1ff
    style E fill:#e1f5ff
```

### TOML Schema 設計

**Project Config** (`.fwauto/config.toml`):

```toml
# 專案特定 scenarios
[[scenarios]]
name = "led-on"
description = "Turn on LED"
binary_args = "on"

[[scenarios]]
name = "led-off"
description = "Turn off LED"
binary_args = "off"

[[scenarios]]
name = "test-mode"
description = "Run in test mode with debug output"
binary_args = "--mode test --verbose"
```

### Changes Required

#### 1. Scenario 資料結構

**File**: `src/fwauto/scenario.py` (新檔案)

**Changes**: 定義 Scenario dataclass

```python
"""Scenario management for FWAuto."""

from dataclasses import dataclass
from typing import Any


@dataclass
class Scenario:
    """Represents a predefined execution scenario."""

    name: str
    description: str
    binary_args: str

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Scenario":
        """Create Scenario from TOML dict."""
        return cls(
            name=data["name"],
            description=data.get("description", ""),
            binary_args=data.get("binary_args", ""),
        )

    def to_dict(self) -> dict[str, Any]:
        """Convert to dict for TOML serialization."""
        return {
            "name": self.name,
            "description": self.description,
            "binary_args": self.binary_args,
        }


class ScenarioManager:
    """Manage scenarios from Project config."""

    def __init__(self, project_scenarios: list[Scenario]):
        """
        Initialize scenario manager.

        Args:
            project_scenarios: Scenarios from Project Config
        """
        self.scenarios = {s.name: s for s in project_scenarios}

    def get_scenario(self, name: str) -> Scenario | None:
        """
        Get scenario by name.

        Returns:
            Scenario object or None if not found
        """
        return self.scenarios.get(name)

    def list_scenarios(self) -> list[Scenario]:
        """
        List all scenarios.

        Returns:
            List of all available scenarios
        """
        return list(self.scenarios.values())
```

#### 2. ProjectConfig 擴展

**File**: `src/fwauto/config.py`

**Changes**: 新增 scenarios 相關方法

```python
from .scenario import Scenario, ScenarioManager

class ProjectConfig:
    # ... 現有代碼 ...

    def get_scenarios(self) -> list[Scenario]:
        """載入 Project Config 中的所有 scenarios"""
        scenarios_data = self.data.get("scenarios", [])
        return [Scenario.from_dict(s) for s in scenarios_data]

    def get_scenario_manager(self) -> ScenarioManager:
        """
        建立 ScenarioManager

        Returns:
            ScenarioManager instance
        """
        project_scenarios = self.get_scenarios()
        return ScenarioManager(project_scenarios)
```

#### 3. CLI 新增 --scenario 選項

**File**: `src/fwauto/cli.py`

**Changes**: flash 命令新增 --scenario 選項

```python
@app.command()
def flash(
    binary_args: str = typer.Option(
        "",
        "--binary-args",
        "-ba",
        help="Arguments to pass to the binary",
    ),
    scenario: str = typer.Option(
        "",
        "--scenario",
        "-s",
        help="Use a predefined scenario (overridden by --binary-args)",
    ),
):
    """
    📥 Flash firmware to device.

    Examples:
        fwauto flash --scenario led-on
        fwauto flash -s test-mode
        fwauto flash --binary-args "custom args"  # Overrides scenario
    """
    console.print("[cyan]📥 Flashing firmware (using .fwauto/ config)...[/cyan]")

    # 優先級：--binary-args > --scenario
    final_binary_args = binary_args

    if not final_binary_args and scenario:
        # 載入 scenario
        try:
            from .config import find_project_root, load_project_config

            project_root = find_project_root()
            if not project_root:
                console.print("[red]❌ Not in a FWAuto project (no .fwauto/ found)[/red]")
                raise typer.Exit(1)

            config = load_project_config(project_root)
            scenario_manager = config.get_scenario_manager()

            scenario_obj = scenario_manager.get_scenario(scenario)
            if not scenario_obj:
                console.print(f"[red]❌ Scenario '{scenario}' not found[/red]")
                console.print("[yellow]💡 List available scenarios: fwauto scenarios list[/yellow]")
                raise typer.Exit(1)

            final_binary_args = scenario_obj.binary_args
            console.print(f"[green]✓[/green] Using scenario: {scenario_obj.name}")
            console.print(f"[dim]  Description: {scenario_obj.description}[/dim]")
            console.print(f"[dim]  Args: {scenario_obj.binary_args}[/dim]")

        except Exception as e:
            console.print(f"[red]❌ Failed to load scenario: {e}[/red]")
            raise typer.Exit(1)

    if final_binary_args:
        console.print(f"[cyan]📦 Binary args: {final_binary_args}[/cyan]")

    state: FirmwareState = {
        # ... 現有欄位 ...
        "command_args": {"binary_args": final_binary_args},
        # ... 現有欄位 ...
    }

    run_workflow(state)
```

#### 4. Scenarios 子命令

**File**: `src/fwauto/cli.py`

**Changes**: 新增 scenarios 命令群組

```python
# 在檔案末尾新增

scenarios_app = typer.Typer(help="Manage execution scenarios")
app.add_typer(scenarios_app, name="scenarios")


@scenarios_app.command("list")
def scenarios_list():
    """
    📋 List all available scenarios.

    Example:
        fwauto scenarios list
    """
    from rich.table import Table
    from .config import find_project_root, load_project_config

    console.print("[cyan]📋 Available Scenarios[/cyan]\n")

    try:
        project_root = find_project_root()
        if not project_root:
            console.print("[yellow]⚠ Not in a FWAuto project[/yellow]")
            raise typer.Exit(1)

        config = load_project_config(project_root)
        scenario_manager = config.get_scenario_manager()
        scenarios = scenario_manager.list_scenarios()

        if not scenarios:
            console.print("[yellow]No scenarios found[/yellow]")
            return

        # 建立表格
        table = Table(show_header=True, header_style="bold cyan")
        table.add_column("Name", style="green")
        table.add_column("Description", style="white")
        table.add_column("Binary Args", style="yellow")

        for scenario in sorted(scenarios, key=lambda s: s.name):
            table.add_row(
                scenario.name,
                scenario.description,
                scenario.binary_args,
            )

        console.print(table)
        console.print(f"\n[dim]Total: {len(scenarios)} scenario(s)[/dim]")

    except Exception as e:
        console.print(f"[red]❌ Error: {e}[/red]")
        raise typer.Exit(1)


@scenarios_app.command("show")
def scenarios_show(name: str):
    """
    🔍 Show details of a specific scenario.

    Example:
        fwauto scenarios show led-on
    """
    from .config import find_project_root, load_project_config

    try:
        project_root = find_project_root()
        if not project_root:
            console.print("[red]❌ Not in a FWAuto project[/red]")
            raise typer.Exit(1)

        config = load_project_config(project_root)
        scenario_manager = config.get_scenario_manager()

        scenario = scenario_manager.get_scenario(name)
        if not scenario:
            console.print(f"[red]❌ Scenario '{name}' not found[/red]")
            raise typer.Exit(1)

        console.print(f"\n[cyan]Scenario: {scenario.name}[/cyan]")
        console.print(f"[white]Description:[/white] {scenario.description}")
        console.print(f"[yellow]Binary Args:[/yellow] {scenario.binary_args}\n")

    except Exception as e:
        console.print(f"[red]❌ Error: {e}[/red]")
        raise typer.Exit(1)
```

### Success Criteria

#### Automated Verification:

- [x] Scenario 資料結構測試：`pytest tests/test_scenario.py -v`
- [x] ScenarioManager 測試：`pytest tests/test_scenario.py::test_scenario_manager -v`
- [x] 配置載入測試：`pytest tests/test_project_config.py::test_get_scenarios -v`

#### Manual Verification:

- [x] 在 Project Config 新增 scenario，執行 `fwauto flash --scenario led-on`，確認載入並使用正確參數
- [x] 執行 `fwauto flash -s test-mode`，確認短選項正常工作
- [x] 執行 `fwauto flash --binary-args "x" --scenario led-on`，確認 --binary-args 優先
- [x] **調整**: 移除 `fwauto scenarios list/show` 子命令，保持簡單（KISS 原則）

**Implementation Note**: 完成此 Phase 並通過所有驗證後，整個計劃完成。

---

## Testing Strategy

### 單元測試

每個 Phase 包含獨立的單元測試：

**Phase 1 測試**:
- `tests/test_user_config.py::test_deployment_config_getters_setters`
- `tests/test_config.py::test_deployment_config_property`
- `tests/test_utils.py::test_execute_make_command_with_env_vars`

**Phase 2 測試**:
- `tests/test_cli.py::test_flash_with_binary_args`
- `tests/test_flash_node.py::test_extract_binary_args`
- `tests/test_flash_node.py::test_reject_dangerous_chars`

**Phase 3 測試**:
- `tests/test_scenario.py::test_scenario_from_dict`
- `tests/test_scenario.py::test_scenario_manager_priority`
- `tests/test_config.py::test_load_scenarios`

### 整合測試

在真實 AM62X 開發板上測試：

```bash
# 1. 初始化專案
fwauto init
# 選擇 AM62X，輸入板子 IP

# 2. 測試連線配置
fwauto flash
# 確認使用 config.toml 中的 IP

# 3. 測試參數傳遞
fwauto flash --binary-args "test"
# 確認遠端執行 ./app test

# 4. 測試 scenarios
# 編輯 .fwauto/config.toml 加入 scenario
fwauto scenarios list
fwauto flash --scenario test-scenario
```

### 手動測試檢查清單

- [ ] Phase 1 完成後，驗證配置繼承（User > Project）
- [ ] Phase 2 完成後，驗證參數安全性（拒絕危險字符）
- [ ] Phase 3 完成後，驗證 scenario 優先級（--binary-args > --scenario）

---

## Performance Considerations

- **配置載入**：所有配置在 node 初始化時一次性載入，無性能影響
- **參數驗證**：簡單的字符串檢查，時間複雜度 O(n)，n 為參數長度
- **Scenario 查詢**：使用字典查詢，時間複雜度 O(1)

無額外的網路請求或磁碟 I/O，對現有 flash 流程的性能影響可忽略。

---

## Migration Notes

### 向後相容性

所有新功能都有預設值，現有使用者無需修改任何配置：

1. **Phase 1**: 若無 deployment config，使用硬編碼預設值
2. **Phase 2**: 若無 --binary-args，BINARY_ARGS 為空字符串
3. **Phase 3**: 若無 --scenario，不載入 scenario

### 升級路徑

```bash
# 現有專案無需任何操作即可升級
git pull
uv sync

# 選擇性：配置 deployment（若需要修改預設 IP）
# 手動編輯 ~/.fwauto/config.toml 或執行 fwauto init

# 選擇性：定義 scenarios（若需要）
# 手動編輯 config.toml 新增 [[scenarios]]
```

---

## References

- 原始研究: `.ai/prpi/research/2025-11-06-am62x-deployment-config.md`
- 相關程式碼:
  - Log 命令參數模式: `src/fwauto/cli.py:175-222`
  - SDK path 繼承模式: `src/fwauto/config.py:92-105`
  - Flash 流程: `src/fwauto/nodes/flash.py:21-129`
  - Utils 工具: `src/fwauto/utils.py:78-155`
  - AM62X 模板: `src/fwauto/templates/sdk_am62x_linux/`

---

## 附錄：配置範例

### 完整 User Config 範例

```toml
# ~/.fwauto/config.toml

[sdk.am62x]
type = "am62x"
path = "/home/alientek/ATK-AM62x-SDK"
version = "1.0.0"

[device.am62x]
family = "am62x"
model = "ATK-AM62x"

[deployment.am62x]
board_ip = "192.168.50.170"
board_user = "root"
deploy_path = "/home/root"
ssh_options = "-o StrictHostKeyChecking=no -o ConnectTimeout=5"
```

### 完整 Project Config 範例

```toml
# .fwauto/config.toml

[sdk]
type = "am62x"

[device]
family = "am62x"
model = "ATK-AM62x"

[build]
target = "build"
makefile = ".fwauto/build/Makefile"

[flash]
target = "flash"
makefile = ".fwauto/flash/Makefile"

# 專案特定的 deployment（覆寫 User Config）
[deployment.am62x]
board_ip = "192.168.1.100"  # 專案使用不同的開發板

# 專案特定的 scenarios
[[scenarios]]
name = "project-test"
description = "Project-specific test scenario"
binary_args = "--config project.conf --debug"
tags = ["project", "test"]
```

---

**計劃版本**: 1.0
**建立日期**: 2025-11-06
**預估總工時**: 4-6 天（Phase 1: 1-2 天，Phase 2: 1-2 天，Phase 3: 1-2 週）
