# ETTerms — Architecture > A native Windows terminal workspace for **SSH** and **Serial Port** connections, > with a TeraTerm-compatible **TTL scripting engine**. > UI inspired by **KKTerm** (activity rail + tabbed session workspace + saved-connection sidebar); > script engine ported and extended from **MyTeraTerm** (`TTLInterpreter`). --- ## Overview ETTerms 是一個給工程師 / 韌體 / 硬體驗證人員用的**單一視窗終端機工作台**。它把日常會用到的兩種連線——**SSH**(連伺服器 / 嵌入式 Linux)與 **Serial Port**(連 UART / console / 開發板)——收進同一個 WinForms 視窗裡,用分頁(Tab)管理多條連線,左側有可存檔的連線清單(Saved Connections)。 核心差異化價值是**腳本自動化**:沿用並擴充 MyTeraTerm 既有的 **TTL(Tera Term Language)直譯器**,讓使用者既有的 `.ttl` 腳本(`send` / `sendln` / `wait` / `if` / `while` / `logopen` / `sprintf2` / `pductrl`…)可以直接重用,直接驅動原生 SSH / Serial channel,做到登入自動化、批次下命令、log 收集、PDU 電源控制等。 開發策略採「**GUI 先行**」:先把 KKTerm 風格的視窗外殼與分頁工作區做出來(可見、可切換、可關閉),再逐步把 Serial → SSH → VT100 渲染 → 腳本引擎一層層補上。 **目標使用者:** 單機桌面使用者(無多人 / 無登入系統)。所有連線資料存在本機 SQLite,密碼存在 Windows Credential Manager,不上雲、不回傳。 --- ## Tech Stack | Layer | Technology | 備註 | |-------|-----------|------| | UI Framework | **C# .NET 8 WinForms**(`net8.0-windows`) | 與 MyTeraTerm 同框架;本機已裝 .NET 8 Desktop Runtime + SDK 9/10(net8 targeting pack 自動還原)| | SSH | **SSH.NET**(`Renci.SshNet`) | 原生 SSH,支援 password / key / keyboard-interactive | | Serial | **System.IO.Ports** | 沿用 MyTeraTerm `ComPortBridge` 經驗 | | Terminal 渲染 | **自繪 VT100 / ANSI 控制項**(owner-drawn `Control`) | 解析 ANSI escape,雙緩衝繪字格 | | Script 引擎 | **TTLInterpreterLib**(從 MyTeraTerm 移植 + 擴充) | 改為驅動 `ISessionChannel` 而非 com0com bridge | | 連線儲存 | **SQLite**(`Microsoft.Data.Sqlite`) | 取代 KKTerm 的 SQLite store;存連線 metadata | | 祕密儲存 | **Windows Credential Manager**(DPAPI / CredMan) | 連線密碼、SSH key passphrase,不落地明碼 | | PDU 控制(選用) | **SnmpSharpNet** | 沿用 MyTeraTerm PDU 控制(`pductrl` / `pduconnect`) | | 日誌 | 自製 **AppLogger**(從 MyTeraTerm 移植) | 檔案 + Debug 雙輸出 | | AI / MCP 整合(選用) | **stdio MCP server**(官方 C# SDK `ModelContextProtocol`) | 兩個獨立 server:`ETTerms.SerialMcp`(不自己開 port,經本機 named pipe 橋接 GUI 持有的 serial session)與 `ETTerms.PduMcp`(直接打 SNMP 控制 PDU 插座,不需 GUI);把收發 / 電源控制暴露成 AI 可呼叫工具(Kiro CLI / Claude CLI),見 [AI / MCP Integration](#ai--mcp-integrationserial-mcp--pdu-mcp-server) | | 內建 AI Assistant(Phase 10 規劃中) | **Microsoft.Extensions.AI**(OpenAI 相容 client + function calling) | GUI 內建 agent 聊天分頁,in-process 直呼 serial / PDU 工具(不經 MCP);**BYO endpoint**——Provider 預設空白,發佈版不含任何私人端點,API key 存 Credential Manager | | 打包 | `dotnet publish` + (選用)Inno Setup / MSIX | 單機安裝,current-user | > **與舊版 MyTeraTerm 的關鍵差異:** 舊版是把真正的 `ttermpro.exe`(TeraTerm)嵌進 Panel,靠 **com0com 虛擬 COM 對**攔截 serial 來跑腳本。ETTerms 改走**全原生**:SSH.NET 做 SSH、`System.IO.Ports` 做 serial、自繪 VT100 控制項做終端機畫面,**不再依賴外部 TeraTerm exe,也不再需要 com0com**。腳本引擎從「驅動 com0com bridge」改成「驅動原生 `ISessionChannel`」。 --- ## Architecture Diagram ``` ┌──────────────────────────────────────────────────────────────────────┐ │ MainForm (WinForms Shell) │ │ │ │ ┌────────────┐ ┌──────────────────┐ ┌────────────────────────────┐ │ │ │ Activity │ │ Connection │ │ Tabbed Workspace │ │ │ │ Rail │ │ Sidebar │ │ (一個 Tab = 一條 Session) │ │ │ │ (左側圖示) │ │ (Saved │ │ │ │ │ │ │ │ Connections) │ │ ┌──────────────────────┐ │ │ │ │ ▣ Terminal │ │ ▸ SSH: srv-01 │ │ │ TerminalView │ │ │ │ │ ▣ Scripts │ │ ▸ SSH: nas │ │ │ (自繪 VT100 控制項) │ │ │ │ │ ▣ Settings │ │ ▸ COM3 @115200 │ │ │ │ │ │ │ │ │ │ ▸ COM7 @9600 │ │ │ bytes ↑↓ │ │ │ │ └────────────┘ └──────────────────┘ │ └──────────┬───────────┘ │ │ │ └─────────────┼──────────────┘ │ └─────────────────────────────────────────────────────┼─────────────────┘ │ ┌────────────────────────────┼───────────────┐ │ ISessionChannel (抽象) │ │ │ ├─ SshChannel (SSH.NET) ─┘ │ │ └─ SerialChannel (System.IO.Ports) │ └──────────────┬──────────────────────────────┘ │ Write(bytes) / DataReceived(bytes) ┌──────────────┴──────────────────────────────┐ │ ScriptEngine (TTLInterpreter, ported) │ │ send / sendln / wait / if / while / │ │ logopen / messagebox / pductrl ... │ └──────────────┬──────────────────────────────┘ │ (選用) ┌──────┴───────┐ │ SNMP PDU │ (SnmpSharpNet) └──────────────┘ ``` **資料流核心抽象:** 所有連線都實作 `ISessionChannel`(`Write(byte[])` + `event DataReceived`)。`TerminalView` 與 `ScriptEngine` 都只認得這個抽象,因此 SSH 與 Serial 對上層完全一致——這是讓「同一套腳本引擎驅動兩種連線」的關鍵設計。 --- ## Project Structure ``` ETTerms/ │ ├── CLAUDE.md # 專案記憶 & 給 Claude 的指令 ├── README.md # 快速上手、打包說明 ├── ARCHITECTURE.md # 本文件 ├── .gitignore # 含 For_AI/ 與 build 產物規則 ├── ETTerms.sln │ ├── docs/ │ ├── architecture.md # (本文件副本 / 連結) │ ├── ttl-script-reference.md # TTL 指令對照表(移植自 MyTeraTerm) │ ├── decisions/ # ADR:為何走原生而非嵌 TeraTerm │ └── runbooks/ # 操作手冊、常見問題(無實際密碼) │ ├── .claude/ │ ├── settings.json │ └── skills/ │ ├── tools/ │ ├── scripts/ # 建置 / 打包腳本 │ └── prompts/ │ ├── For_AI/ # 🚫 gitignored — AI 協作素材 │ ├── KKTerm-main/ # UI 參考專案(Tauri/React 工作台) │ └── MyTeraTerm/ # Script 參考專案(舊版 WinForms) │ └── src/ ├── ETTerms/ # 主應用程式(WinForms) │ ├── Program.cs # 進入點 ├── ETTerms.csproj # net8.0-windows, UseWindowsForms │ ├── App/ # ── 視窗外殼 (UI Shell) ── │ ├── MainForm.cs # 主視窗:Rail + Sidebar + Workspace(含深色標題列 DWM) │ ├── MainForm.Designer.cs │ ├── Theme.cs # 全域深色配色 (KKTerm 風格) │ ├── ActivityRail.cs # 左側圖示列 (Terminal/Status/Settings/About) │ ├── ConnectionSidebar.cs# 仿 KKTerm 可編輯資料夾樹(搜尋/CRUD/拖曳分類) │ ├── StatusView.cs # Status 檢視:分頁式(PDU…),PDU 連線後每 3 秒背景輪詢插座狀態,並可用表格內 Control 鈕直接開關各 Port │ ├── SettingsView.cs # Settings 檢視:分頁式(Terminal / AI MCP) │ ├── AboutView.cs # About 檢視(版本 / 連結) │ ├── Dialogs/ # ── 深色對話框 ── │ │ ├── DarkDialog.cs # 對話框基底(深色 + DWM 標題列) │ │ ├── TextPromptDialog.cs # 單行輸入(資料夾命名 / 改名) │ │ └── ConnectionEditDialog.cs # 連線新增/編輯(名稱+類型+詳細,Phase 2 擴充) │ └── Workspace/ # ── 可平鋪 (tiling) 的工作區 ── │ ├── WorkspaceView.cs # 工具列(grid 預設) + split 樹管理 + active pane 路由 │ ├── PaneControl.cs # 單一格子:自繪迷你分頁列 + 內容 + +↔↕✕ 動作鈕 │ └── ProportionalSplit.cs# 按比例縮放的 SplitContainer (巢狀組成任意佈局) │ ├── Terminal/ # ── 終端機渲染 ── │ ├── TerminalView.cs # 自繪 VT100 控制項 (owner-drawn) │ ├── DarkScrollBar.cs # 自繪深色垂直捲軸 (細長/圓角滑塊, 配深色主題) │ ├── AnsiParser.cs # ANSI/VT100 escape 解析狀態機 (含 DEC 2004 bracketed paste) │ ├── ScreenBuffer.cs # 字格緩衝 (rows×cols, 屬性/顏色) │ └── TerminalInput.cs # 鍵盤 → byte 序列 (含特殊鍵) │ ├── Sessions/ # ── 連線抽象 ── │ ├── ISessionChannel.cs # Write(byte[]) + event DataReceived │ ├── SshChannel.cs # SSH.NET 實作 (ShellStream) │ ├── SerialChannel.cs # System.IO.Ports 實作 │ ├── ShellChannel.cs # Windows ConPTY 本機 Shell(StartupDirectory 不存在時 fallback 使用者家目錄) │ ├── SessionPage.cs # 一個分頁 = TerminalView + Channel + 狀態 │ ├── SessionManager.cs # 開 / 關 / 列舉所有 active session │ ├── SerialBridgeServer.cs# ✅ 本機 named pipe server:把 serial session 的讀寫橋接給 MCP(Phase 9) │ └── SerialBridge.cs # ✅ SerialBridgeEndpoint:單一 serial session 與 pipe 的橋接點 │ ├── Connections/ # ── 連線資料 ── │ ├── Connection.cs # 連線 metadata 模型 │ ├── ConnectionStore.cs # SQLite CRUD │ └── CredentialVault.cs # Windows Credential Manager 封裝 │ ├── Scripting/ # ── TTL 腳本引擎(移植 + 擴充)── │ ├── TTLInterpreter.cs # 主直譯器 (port 自 MyTeraTerm) │ ├── ScriptRunner.cs # 非同步執行 + 取消 + 進度事件 │ ├── GroupSyncContext.cs # Group 同步 (Barrier):waitall / sendlnall / sendlngroup │ └── Pdu/ │ └── PduController.cs# SnmpSharpNet PDU 控制 (pductrl / pduconnect) │ ├── Ai/ # ── 內建 AI Assistant(Phase 10, v0.6.0)── │ ├── OpenAiChatClient.cs # 極簡 OpenAI 相容 /chat/completions(HttpClient, 非串流;BYO endpoint) │ ├── AgentHost.cs # 手寫 agent loop(tool_calls → 執行 → 餵回 → 迴圈,上限 8 輪) │ └── AiTools.cs # 工具集:serial(經 SerialBridge, [AI] echo)+ PDU(PduCore);破壞性動作經 ConfirmAsync 彈框 │ └── Infrastructure/ ├── AppLogger.cs # 日誌 (port 自 MyTeraTerm) ├── AppSettings.cs # 使用者偏好 (JSON, %LocalAppData%\ETTerms\settings.json) ├── McpRegistrar.cs # ✅ 一鍵把 Serial MCP 註冊/移除到 Claude Code / Kiro 設定檔(Settings → AI MCP) └── NativeTheme.cs # 深色標題列 (DWM) │ └── ETTerms.SerialMcp/ # ✅ Serial MCP server(stdio)——不自己開 port,經 named pipe 橋接 GUI ├── Program.cs # stdio MCP host 進入點 ├── SerialBridgeClient.cs # 連 GUI 的 named pipe,轉發 write / 接收 RX ├── SerialTools.cs # serial_list / attach / write / read / detach 工具(轉發到 pipe) └── ETTerms.SerialMcp.csproj# net8.0 console + ModelContextProtocol SDK ``` > **`For_AI/` 內含兩份參考專案**:`KKTerm-main`(UI 參考,Tauri+React 的 Windows 工作台)與 `MyTeraTerm`(Script 參考,舊版嵌 TeraTerm 的 WinForms)。整個 `For_AI/` 已 gitignore,僅供開發時對照,不進 repo。 > **無 `secret/` 資料夾:** ETTerms 是桌面單機 App,**沒有 DB 密碼 / 連線字串 / API key 之類的伺服端祕密,也無 compile-time secret**。連線密碼一律存 Windows Credential Manager(不落地明碼),因此不需要 `secret/` 集中管理機制。 --- ## Data Models 連線 metadata 存在本機 SQLite(`%LocalAppData%\ETTerms\ettermsdb.sqlite`)。**密碼 / passphrase 不存在這裡**,只存一個指向 Windows Credential Manager 的 `CredentialKey`。 ```csharp public enum ConnectionType { Ssh = 0, Serial = 1 } // 主連線模型 public class Connection { public Guid Id { get; set; } public string Name { get; set; } = ""; // 顯示名稱,例:srv-01 / COM3 board public ConnectionType Type { get; set; } public int SortOrder { get; set; } // sidebar 排序 public string? GroupName { get; set; } // 選用:分組 (folder) public DateTime LastUsedUtc { get; set; } // SSH 專用(Type == Ssh 時有效) public SshSettings? Ssh { get; set; } // Serial 專用(Type == Serial 時有效) public SerialSettings? Serial { get; set; } // 指向 Windows Credential Manager 的 key,例:"ETTerms/{Id}" // 明碼密碼絕不存進 SQLite public string? CredentialKey { get; set; } } public class SshSettings { public string Host { get; set; } = ""; public int Port { get; set; } = 22; public string Username { get; set; } = ""; public SshAuthMethod AuthMethod { get; set; } // Password / PrivateKey / KeyboardInteractive public string? PrivateKeyPath { get; set; } // key 檔路徑(passphrase 走 CredentialVault) } public enum SshAuthMethod { Password = 0, PrivateKey = 1, KeyboardInteractive = 2 } public class SerialSettings { public string PortName { get; set; } = "COM1"; // COM3, COM7... public int BaudRate { get; set; } = 115200; public int DataBits { get; set; } = 8; public Parity Parity { get; set; } = Parity.None; // System.IO.Ports.Parity public StopBits StopBits { get; set; } = StopBits.One; public Handshake Handshake { get; set; } = Handshake.None; public string NewLine { get; set; } = "\r\n"; // 送出換行序列 } // 終端機偏好(存 AppSettings,非每連線) public class TerminalProfile { public string FontFamily { get; set; } = "Cascadia Mono"; public float FontSize { get; set; } = 11f; public int Cols { get; set; } = 80; public int Rows { get; set; } = 24; public string Theme { get; set; } = "dark"; // 配色名稱 public int ScrollbackLines { get; set; } = 5000; } ``` **SQLite Schema(單表即可起步):** | 欄位 | 型別 | 說明 | |------|------|------| | `Id` | TEXT (GUID) | 主鍵 | | `Name` | TEXT | 顯示名稱 | | `Type` | INTEGER | 0=Ssh, 1=Serial | | `SortOrder` | INTEGER | sidebar 排序 | | `GroupName` | TEXT NULL | 分組 | | `LastUsedUtc` | TEXT | ISO8601 | | `SettingsJson` | TEXT | `SshSettings` / `SerialSettings` 序列化 | | `CredentialKey` | TEXT NULL | Credential Manager 索引鍵 | --- ## Authentication & Authorization **不適用。** ETTerms 是單機桌面工具,沒有使用者帳號 / 登入 / 角色系統。 唯一相關的「認證」是**對外連線時的 SSH 認證**(password / private key / keyboard-interactive),其憑證透過 **Windows Credential Manager** 儲存與讀取,由 `CredentialVault.cs` 封裝。詳見 [Security Considerations](#security-considerations)。 --- ## Key Pages / Features ETTerms 是單視窗多分頁,沒有「路由」,以下以**功能面板**為單位描述。 ### Activity Rail(左側圖示列) - 切換主檢視:**Terminal**(連線工作區)/ **Scripts**(腳本編輯與執行)/ **Settings**(偏好設定) - 仿 KKTerm 的 ActivityRail,hover 顯示 tooltip ### Connection Sidebar(Saved Connections,仿 KKTerm 資料夾樹) - **使用者可自建資料夾**,把連線分類組織成任意層的目錄樹(資料夾可巢狀) - 每個資料夾顯示**連線數量徽章**、可展開 / 收合(工具列有「全部展開 / 收合」) - **搜尋框**:依名稱 / 主機即時過濾,命中的分支自動展開 - 圖示區分 SSH / Serial,顯示主機或 COM port + baud - 操作(右鍵選單 + 工具列按鈕):新增資料夾 / 新增連線 / 重新命名 / 刪除 - **拖曳分類**:把連線或資料夾拖進別的資料夾(禁止拖進自己的子孫) - 雙擊連線 → 在 **active pane** 開啟;「快速連線」→ 不存檔的 ad-hoc 連線 - Phase 1 資料存記憶體;**Phase 2 換成 `ConnectionStore`(SQLite)持久化** ### Workspace(可平鋪 tiling 的工作區) - 工作區可分割成多個 **pane(格子)**,自由排列(仿 KKTerm grid): - **Grid 預設**:工具列一鍵切 `1×1 / 1×2 / 2×1 / 2×2 / 2×3 / 3×3` - **手動分割**:每個 pane 右上角 `↔`(左右分割)/ `↕`(上下分割),拖格線(`ProportionalSplit`)按比例調整大小 - 關閉某格時,兄弟節點自動補位(collapse split);保留至少一格 - **每個 pane 內可再開多條連線分頁**(pane 自己的迷你 tab strip) - **active pane** 以強調色外框標示;側欄雙擊連線會開進 active pane - 一條連線分頁 = `SessionPage`(`TerminalView` + `ISessionChannel`,Phase 3+ 接上) ### Terminal View(自繪 VT100) - 接收 channel bytes → `AnsiParser` → `ScreenBuffer` → 繪製 - 鍵盤輸入 → `TerminalInput` → byte 序列 → channel - 支援:scrollback、選取 / 複製、貼上、字型 / 配色(從 `TerminalProfile`) - **捲動:** 滑鼠滾輪或右側自繪深色捲軸 `DarkScrollBar`(拖曳滑塊 / 點軌道翻頁);`ContentWidth` 扣掉捲軸寬避免文字被蓋,捲軸範圍 / 位置在 `Feed` / 滾輪 / resize 時同步 - **貼上:** 對方啟用 bracketed paste(DEC mode 2004,如 PSReadLine / Kiro CLI)時,整段以 `ESC[200~ … ESC[201~` 包夾送出,避免多行貼上被逐行 Enter 立即送出;未啟用則逐字送出 - **複製:** 右鍵複製選取內容後自動清除反白(提示已複製) ### Script Editor / Runner(Scripts 檢視) - 載入 / 編輯 `.ttl` 腳本(語法沿用 MyTeraTerm) - 對「目前 active session」執行腳本;顯示執行進度(檔名 / 行號 / 當前指令) - 可取消執行(`Cancel()`);`logopen` 將輸出寫檔 - `sprintf2`:C `printf` 風格格式化輸出到變數(TeraTerm 相容) - `wait`:命中關鍵字後會等裝置安靜(`SettleMs`,預設 300ms)再接受並取最後一次出現,避免比對到輸出中途的指令回顯 - 選用:PDU 控制指令(`pductrl` / `pduconnect`)走 SNMP ### Group 同步執行 - 分頁可透過**右鍵 Tab** → 設為 Group 1 / 2 / 3(或取消),cell footer 顯示 `[Group1-A]` 標籤 - Toolbar 的 `▶ Group1` / `▶ Group2` / `▶ Group3` 按鈕對整個 Group 同時跑同一份 `.ttl` 腳本 - Group 模式支援同步指令:`waitall`(全員 wait 到關鍵字再繼續)、`sendlnall`(全員到齊後各自 sendln)、`sendlngroup`(指定 member 才送) - `▶ Run All` 和分頁 `▶ Script` 會拒絕含 Group 指令的腳本(彈 Warning) - 同步機制使用 `System.Threading.Barrier`(`GroupSyncContext`),確保成員在同步點等齊 ### Settings - 終端機字型 / 字級 / 配色 / scrollback 行數 - 預設換行序列、編碼 - 視窗位置記憶 --- ## Data Flow **SSH session 端到端:** ``` 使用者雙擊 Sidebar 連線 → SessionManager.Open(connection) → CredentialVault.Get(connection.CredentialKey) 讀密碼 → new SshChannel(SshSettings, credential) → SSH.NET SshClient.Connect() → ShellStream 建立 → new SessionPage(TerminalView, channel) 加入 WorkspaceTabs 執行期雙向資料流: 鍵盤 → TerminalInput → bytes → SshChannel.Write() → ShellStream ShellStream → SshChannel.DataReceived(bytes) → TerminalView → AnsiParser → ScreenBuffer → Invalidate() → 繪製 ``` **腳本驅動流(與互動式共用同一 channel):** ``` ScriptRunner.RunAsync(scriptText, activeChannel) → TTLInterpreter(channel) send/sendln → channel.Write(bytes) wait "xxx" → 監聽 channel.DataReceived,比對到關鍵字後再等裝置「安靜」(~300ms 無新資料) 才接受, 並消費到該字串「最後一次」出現處 → 避免命中輸出中途的指令回顯(如 SVOS> help) sprintf2 → C printf 風格格式化字串存入變數(與 TeraTerm 相容,設定 result) logopen/write→ StreamWriter 寫檔 pductrl → PduController(SNMP set)→ PDU if/while → 依 result / 變數做流程控制 → StatusChanged 事件 → UI 顯示「檔名 第N行 指令」 ``` **Serial session:** 與 SSH 相同,只是 `ISessionChannel` 換成 `SerialChannel`(`System.IO.Ports.SerialPort` 的 `DataReceived` / `Write`)。上層 `TerminalView` 與 `TTLInterpreter` 完全不需改動——這正是 `ISessionChannel` 抽象的價值。 --- ## AI / MCP Integration(Serial MCP + PDU MCP Server) ETTerms 提供**兩個獨立的 stdio MCP server**給 AI agent(Kiro CLI / Claude CLI): - **`ETTerms.SerialMcp`** — 收發 serial。COM port 獨佔,故由 GUI 唯一持有、MCP 經本機 named pipe 橋接(見下方)。 - **`ETTerms.PduMcp`** — 控制 SNMP PDU 電源插座。SNMP(UDP) 非獨佔,故 MCP **直接打 SNMP**,不需 GUI 在跑、也不經 pipe。 兩者都能用 GUI **Settings → AI MCP** 一鍵 Setup(`McpRegistrar` 會同時註冊 `etterms-serial` 與 `etterms-pdu`)。 ### Serial MCP Server > 讓 **Kiro CLI / Claude CLI** 等 AI agent 收發 serial,**且使用者能在 ETTerms GUI 即時看到 AI 的每筆收發**。 > > **關鍵設計:COM port 由 GUI 唯一持有,MCP server 不自己開 port。** 一個 COM port 同一時間只能被一個行程開啟;若讓 MCP server 自己開,GUI 就無法同時開、使用者也看不到。因此改成 **GUI 當 port 的唯一擁有者**,在 GUI 內跑一支本機 **named pipe server**(`SerialBridgeServer`);獨立的 `ETTerms.SerialMcp`(由 Kiro/Claude CLI 啟動)退化成**瘦客戶端**,所有 `write` / `read` 都經 pipe 轉發給 GUI,由 GUI 代為讀寫實體 port。 ### 全閉迴路(推薦用法) 整個迴路可全部跑在 ETTerms GUI 內,使用者一邊看、AI 一邊操作: ``` ETTerms GUI(單一行程,唯一開 COM3 的人) │ ├─ Tab1: Serial 連線 ── SerialChannel 實體持有 COM3 │ TerminalView 即時顯示(AI 的 TX 以 [AI] 標色,RX 照常顯示) │ ├─ SerialBridgeServer(本機 named pipe: \\.\pipe\etterms-serial) │ ▲ write 轉發 / RX 廣播 │ │ └─ Tab2: PowerShell (ConPTY) 跑 kiro-cli └─ kiro 啟動子行程 ETTerms.SerialMcp(stdio / JSON-RPC) └─ 不開 COM,連上面的 pipe → 收發都流經 GUI 的 channel ``` **資料流:** - **AI 送資料:** `serial_write` → pipe → GUI `SerialChannel.Write()` 送出 COM3,**同時把這段 echo 進 Tab1 TerminalView(`[AI]` 標色)** → 使用者看得到 AI 打了什麼。 - **裝置回資料:** COM3 RX → GUI 一邊顯示在 Tab1、一邊經 pipe 廣播 → `serial_read` 取出 → AI 讀到。 因為 RX/TX 物理上都流經 GUI 的 channel,**使用者在 GUI 看到的就是 AI 看到的**,完全同步。 ### 暴露的工具 | 工具 | 參數 | 說明 | |------|------|------| | `serial_list` | — | 列出 **GUI 目前開著的 serial session**(名稱 + COM port + baud),供 AI 選定要操作哪一個 | | `serial_attach` | portName \| sessionName | 經 pipe 綁定到 GUI 某個已開啟的 serial session(之後 read/write 都對它) | | `serial_write` | text, appendNewLine? | 經 pipe 請 GUI 對綁定的 session 送出文字(GUI 同步 echo 到 TerminalView) | | `serial_read` | waitFor?, timeoutMs? | 取出該 session 累積的 RX;可等待特定字串或逾時 | | `serial_detach` | — | 解除綁定(**不關閉 GUI 的 port**,GUI 仍持有) | > 與舊版規劃的差異:不再有「MCP 自己 `serial_open` / `serial_close` 實體 port」;改為 `serial_attach` / `serial_detach` 綁定 / 解除 GUI 既有 session。port 的開關一律在 GUI 操作。 ### named pipe 協議(精簡) - **傳輸:** Windows named pipe(`\\.\pipe\etterms-serial`),純本機、不開網路埠。 - **訊息:** 換行分隔的 JSON,例:`{"op":"write","session":"COM3","data":"...","newline":true}` / `{"op":"read","session":"COM3","waitFor":"OK","timeoutMs":3000}` / `{"op":"list"}`。 - **RX 推送:** GUI 主動把 RX 以 `{"op":"rx","session":"COM3","data":"..."}` 推給已連線的 client,client 端累積成 buffer 供 `serial_read` 消費。 - **找不到 session:** AI `attach` / `write` 一個 GUI 沒開的 port 時,回明確錯誤(要求使用者先在 GUI 開啟)。 ### 設計重點 - **技術:** `ETTerms.SerialMcp` 為 .NET 8 console(`net8.0`,無 WinForms)+ 官方 C# MCP SDK(`ModelContextProtocol`),stdio / JSON-RPC;GUI 端的 `SerialBridgeServer` 用 `System.IO.Pipes`。 - **port 擁有權單一化:** 實體 `SerialPort` 只有 GUI 開,根除「兩個行程搶同一 COM」的問題。 - **可視性:** AI 的 TX 在 GUI 以 `[AI]` 標色,與使用者手打的輸入區分;RX 兩邊同源。 - **安全:** 本機、無雲、不碰 credential;pipe 僅限本機行程,只搬 serial bytes。 ### 註冊(兩種方式) **方式 A — 一鍵設定(推薦,v0.2.1):** GUI **Settings → AI MCP** 分頁,對 Claude Code / Kiro 各按 **Setup** 即可。`McpRegistrar` 以 read-modify-write 把 `etterms-serial` 寫進該 CLI 的使用者層級設定檔(Claude Code:`~/.claude.json`;Kiro:`~/.kiro/settings/mcp.json`),保留檔內其他既有 MCP server,原子寫回避免壞檔。註冊的執行檔路徑指向 `\ETTerms.SerialMcp\ETTerms.SerialMcp.exe`(與 publish 慣例對齊,必定存在);卡片同時顯示 CLI 驗證指令,按 **Remove** 可移除。 **方式 B — 手動 CLI:** ```powershell kiro-cli mcp add --name serial --command dotnet ` --args "run --project src\ETTerms.SerialMcp\ETTerms.SerialMcp.csproj" ``` 或寫進 agent.json 的 `mcpServers`;Claude CLI 則用其對應的 `mcpServers` 設定。**使用前提:先在 ETTerms GUI 開好要操作的 serial 連線**,AI 才能 `serial_attach` 上去。註冊後即可對 AI 說「列出目前 serial session → 接上 COM3 → 送指令看回應」。 ### PDU MCP Server(v0.3.0) > 讓 AI agent 直接控制 SNMP PDU 的電源插座,**典型用途:測試中自動 power-cycle DUT**。 **關鍵設計:直接打 SNMP,不經 GUI 橋接。** 與 serial 不同,PDU 走 SNMP(UDP)**非獨佔**——多個行程可同時對同一台 PDU 下命令。因此 `ETTerms.PduMcp` 不需要像 serial 那樣繞 GUI 的 named pipe,而是內含一份精簡版 `PduController`(OID 邏輯複製自 GUI 的 `Scripting/Pdu/PduController`,診斷改走 stderr 以免污染 stdio JSON-RPC)直接與 PDU 對話。**好處:GUI 不必開著,AI 也能控制 PDU;最少程式碼、最穩。** ``` Kiro/Claude CLI ── 啟動子行程 ETTerms.PduMcp(stdio / JSON-RPC) └─ SnmpSharpNet ──(SNMP/UDP 161)──► PDU(iPoMan II/III) ``` 連線狀態(device IP → controller)以行程內單例 `PduRegistry` 保存,跨工具呼叫保留,直到 `pdu_disconnect` 或行程結束。 **暴露的工具:** | 工具 | 參數 | 說明 | |------|------|------| | `pdu_connect` | ip | 以 SNMP 連線並驗證 PDU 回應,成功回傳 model name;控制前必須先呼叫 | | `pdu_list` | — | 列出本 session 已連線的 PDU(依 IP) | | `pdu_set_port` | ip, port, on | 將某插座開(on=true)/關(off=false) | | `pdu_get_port` | ip, port | 讀單一插座的狀態 / 電流(mA) / 功率(W) | | `pdu_status` | ip | 讀全部 12 個插座的狀態 / 電流 / 功率 | | `pdu_power_cycle` | ip, port, offSeconds? | 關 → 等 offSeconds → 開(重啟 DUT) | | `pdu_disconnect` | ip | 解除本 session 的 PDU 連線(不改變插座狀態) | > 所有工具回傳統一的 `{ "ok": bool, "result"/"error": ... }` JSON。SNMP community 目前沿用 GUI 版的 `"private"`。 ### 內建 AI Assistant(Phase 10 — ✅ 已完成 v0.6.0) > 讓 ETTerms **自己就是 agent host**——不經 Claude / Kiro,在 GUI 內建 AI 聊天**分頁**,用自然語言驅動 serial 與 PDU(「接上 COM3,送 help 看回應」「連上 PDU,把 outlet 3 重開」一句話完成)。**AI 分頁是 Workspace 的一種 pane**(工具列 `✨ AI Chat` 開啟),可用 Layout(1×2 / 2×2…)與 serial 分頁**並排同時使用**——像 Claude Code / Kiro 那樣一邊聊、一邊看終端機。 **設計原則:BYO Endpoint(使用者自帶 LLM 端點)** - Provider 設定(**Base URL / API Key / Model**)預設**全空白**;未設定時 AI 面板顯示「未設定 AI Provider」且功能完全停用——**發佈出去的 ETTerms 不內含任何端點,對一般使用者就是一個沒有 AI 的終端機**。 - API Key 只存 **Windows Credential Manager**(`ETTerms/AiApiKey`,沿用 `CredentialVault`);Base URL / Model 存 settings.json(本機、非 repo)。 - 介面走 **OpenAI 相容 `/v1/chat/completions` + function calling**,任何供應商皆可接:本機 Ollama(`http://localhost:11434/v1`)、自架 LiteLLM gateway、公司內部 gateway、OpenAI 等。模型需支援 function calling。 - 🚫 **鐵則:任何私人端點 URL / API key 不得出現在程式碼、預設值、文件範例、repo、publish 產物。** 文件範例一律用 `localhost` 或占位符。 **架構(in-process function calling,不經 MCP)** ``` AiChatView(Workspace 的一種 pane,與 SessionPage 並排;工具列 ✨ AI Chat 開啟) └─ AgentHost(Ai/AgentHost.cs):維護對話歷史 + 手寫 agent loop(最多 8 輪工具) ├─ OpenAiChatClient(Ai/OpenAiChatClient.cs):極簡 OpenAI 相容 /chat/completions(非串流)→ 使用者設定的 Base URL └─ AiTools(Ai/AiTools.cs):in-process 直呼,不經子行程 / pipe ├─ serial_list / attach / write / read → SerialBridge 同一套路徑([AI] 標色照舊;read 端自行累積 RX) └─ pdu_connect / status / set_port / power_cycle → ETTerms.PduCore(本 session 內 IP→controller 登錄) ``` **為何內建 agent 不經 MCP:** MCP 解決的是「跨行程 / 跨信任邊界」(Claude / Kiro 是別人的 process,所以需要 stdio JSON-RPC + named pipe 橋接);內建 agent 與工具在**同一個 process**,直呼即可,插一層「子行程 + 序列化 + pipe」繞一圈回自己記憶體裡的物件,只增加故障面。既有的 SerialMcp / PduMcp **不受影響**,繼續服務外部 AI CLI(Settings → AI MCP)。日後若要開放「使用者掛第三方 MCP server」(ETTerms 作為 **MCP host**),把 MCP client 列出的工具 schema concat 進 `AiTools.GetSchemas()` 的清單即可,agent loop 不需改動。 **安全設計:** - 破壞性 PDU 動作(`pdu_set_port` off / `pdu_power_cycle`)一律 **C# 端彈確認框**(`AiTools.ConfirmAsync` → GUI MessageBox,預設按鈕 No;不信 LLM 自律)。 - 所有 AI 工具呼叫寫 **AppLogger** 留跡(`[AI tool] `)。 - Serial TX 沿 Phase 9 慣例以 `[AI]` 標色 echo(`SerialBridgeEndpoint.Write`),使用者全程看得到 AI 打了什麼。 - **工具呼叫上限可設定**(`AppSettings.AiMaxToolRounds`,Settings → AI Assistant):單次訊息最多鏈幾輪工具的保險,**0 = 無上限**(給放著跑一天的自動化腳本;每輪都燒 token,執行中聊天視窗的 **Stop** 鈕可隨時中止,經 `CancellationToken`)。預設 30。 **典型應用(搭配自架 OpenAI 相容 gateway = 硬體工程助理):** 把 Provider 指向你自己的 LLM gateway(本機 Ollama / LiteLLM / 公司 gateway…),ETTerms 就變成一個能用自然語言操作實體硬體的助理:查/送 serial console 指令、依裝置回應判斷、控制 PDU power-cycle DUT、跑重複性測試序列(工具上限設 0 可長跑)。AI 的 serial TX 以 `[AI]` 顯示在終端機,與手動操作同一條 channel,所見即所得。⚠️ 端點由使用者自帶,發佈版不含任何端點(見下方 Security)。 **實作選型:** 手寫 `OpenAiChatClient`(HttpClient + System.Text.Json,非串流)+ 手寫 agent loop,**不引入 `Microsoft.Extensions.AI`**——依賴最小、對任意 OpenAI 相容 gateway 相容性自己掌控、無額外 NuGet 演進風險。工具 schema 為手組 JSON(OpenAI function-calling 格式)。 --- ## Key Constraints & Business Rules 1. **單機、無雲:** 所有連線 metadata 存本機 SQLite,密碼存 Windows Credential Manager,不回傳任何遙測。 2. **密碼絕不落地明碼:** SQLite 只存 `CredentialKey`,實際密碼 / passphrase 一律走 Credential Manager。 3. **一條連線一個分頁:** 同一連線可開多個分頁(各自獨立 session),但每個分頁綁定一個 `ISessionChannel`。 4. **腳本對 active session 執行:** TTL 腳本只作用在目前選定的分頁 channel,不會跨分頁亂送。例外:**Group 模式**下 `waitall` / `sendlnall` / `sendlngroup` 可跨同 Group 成員同步。 5. **腳本可取消:** 長時間 `wait` / `while` 必須能被使用者中止(`isCancelled` 旗標 + `OperationCanceledException`)。 6. **Serial port 互斥:** 一個 COM port 同時只能被一個 session 開啟;開啟前需檢查可用性。 7. **VT 相容性以常見情境為準:** VT100 / 常見 ANSI 序列優先;冷門 escape 可後補,不阻塞 GUI 進度。 8. **GUI 先行:** Phase 1–2 必須先讓視窗外殼 + 分頁 + 假連線可見可操作,再接真實 channel。 9. **不依賴外部 exe:** 不嵌 TeraTerm、不需 com0com;全原生 .NET 元件。 10. **UI 不可被 channel I/O 阻塞:** channel 讀寫在背景,UI 更新一律 `Invoke` 回 UI thread。 11. **AI Provider 預設空白(BYO endpoint):** 內建 AI(Phase 10)未設定端點時完全停用;**任何私人端點 / 金鑰不得進程式碼、預設值、文件範例、publish 產物**。API key 只存 Windows Credential Manager。 --- ## Security Considerations - **密碼儲存:** 一律使用 **Windows Credential Manager**(透過 `CredentialVault.cs`)。SQLite 內只存索引 `CredentialKey`,無明碼。SSH private key passphrase 同理。 - **SSH host key 驗證:** 首次連線顯示 host key 指紋供使用者確認(trust-on-first-use),記錄已信任的指紋,之後比對;指紋不符要警告。 - **私鑰檔保護:** private key 路徑存設定,但不複製 key 內容進 repo / SQLite。 - **AI Provider(Phase 10):** Base URL / Model 存本機 settings.json、API key 只存 Credential Manager(`ETTerms/AiApiKey`);**無任何預設端點**——發佈產物內不含開發者私人伺服器資訊,文件範例一律 `localhost` / 占位符。AI 工具呼叫全程 AppLogger 留跡,破壞性 PDU 動作需 GUI 確認。 - **輸入處理:** 終端機輸入直接透傳給遠端,不做 shell 注入解讀(本來就是終端機);但 UI 載入腳本檔時要防路徑穿越 / 過大檔。 - **日誌不含密碼:** `AppLogger` 與 `logopen` 輸出不可寫入密碼 / passphrase;連線資訊只記主機 / port,不記 credential。 - **無 `secret/` 資料夾:** ETTerms 無伺服端祕密 / DB 密碼 / compile-time secret,連線密碼一律走 Windows Credential Manager,因此不設 `secret/` 集中目錄,也不需要 publish 類腳本。若日後做 Release 程式碼簽章,簽章 `.pfx` 請放在 repo 外並以環境變數 / CI secret 傳入。 - **設定檔權限:** SQLite 與設定檔放在 `%LocalAppData%\ETTerms\`,跟隨使用者帳號 ACL。 --- ## Build & Setup Steps ```powershell # 0. 前置:.NET 8 SDK(或 SDK 9/10 + .NET 8 Desktop Runtime)。確認: dotnet --list-sdks dotnet --list-runtimes | findstr WindowsDesktop # 需有 8.0.x # 1. 建立方案與專案(Phase 1) cd F:\10_AI\ETTerms dotnet new sln -n ETTerms dotnet new winforms -n ETTerms -o src\ETTerms -f net8.0 dotnet sln add src\ETTerms\ETTerms.csproj # 2. 安裝 NuGet 套件 cd src\ETTerms dotnet add package SSH.NET # 原生 SSH dotnet add package System.IO.Ports # Serial dotnet add package Microsoft.Data.Sqlite # 連線 metadata dotnet add package SnmpSharpNet # 選用:PDU 控制 # Windows Credential Manager:用 CredentialManagement 套件或 P/Invoke advapi32 # 3. 設定 csproj(手動確認) # net8.0-windows # true # enable # 4. 建置與執行 cd F:\10_AI\ETTerms dotnet build dotnet run --project src\ETTerms\ETTerms.csproj # 5. 打包(見下方「Publish / 打包慣例」) dotnet publish src\ETTerms\ETTerms.csproj -c Release -r win-x64 --self-contained false ` -o src\ETTerms\Publish\ETTerms_v # 後續可用 Inno Setup / MSIX 做安裝程式 ``` --- ## Publish / 打包慣例 > 每次發 Release 都依此規則產出,方便辨識版本與一併攜帶 MCP server。 **輸出位置(固定):** `src\ETTerms\Publish\ETTerms_v{Version}\` - `{Version}` 取自 `src\ETTerms\ETTerms.csproj` 的 ``(例:`0.2.0`)。 - 資料夾命名:`ETTerms_v{Version}`(前綴 `v`,例:`ETTerms_v0.2.0`)。 - `Publish/` 已 gitignore(`publish/`),**不入 git**。 **內容結構:** ``` ETTerms_v0.3.0\ ├── ETTerms v0.3.0.exe # 主程式 apphost,改名為「ETTerms v{Version}.exe」 ├── ETTerms.dll + 各相依 dll # SSH.NET / SQLite / SnmpSharpNet / System.IO.Ports … ├── ETTerms.SerialMcp\ # Serial MCP server,獨立發佈到子資料夾(相依 dll 與 GUI 隔離) │ ├── ETTerms.SerialMcp.exe │ └── ETTerms.SerialMcp.dll + 相依 └── ETTerms.PduMcp\ # PDU MCP server(v0.3.0),同樣獨立發佈到子資料夾 ├── ETTerms.PduMcp.exe └── ETTerms.PduMcp.dll + 相依(含 SnmpSharpNet) ``` **規則:** 1. **GUI publish 會自動帶上兩個 MCP server**:`ETTerms.csproj` 有 `PublishMcpServers` target(`AfterTargets="Publish"`),會把 `ETTerms.SerialMcp` 與 `ETTerms.PduMcp` 一併發佈到各自的 `\\` **子資料夾**(與 GUI 相依 dll 隔離)。因此**只要發佈 GUI 一個指令**即可,不必再單獨發 MCP。 - 對齊 `McpRegistrar.ResolveServerExe()`:它解析的 `\\.exe` 因此**必定存在**,AI MCP 一鍵設定寫進去的路徑才不會落空。 - MCP 子發佈會**跟隨 GUI 的 `SelfContained` 設定**(target 內以 `$(SelfContained)` 傳入):框架相依版的 MCP 也框架相依;portable 版的 MCP 也免 runtime。 2. **主 exe 改名**:`dotnet publish` 產生的 `ETTerms.exe` 重新命名為 **`ETTerms v{Version}.exe`**。 - 可安全改名:.NET apphost 內部記錄要載入的 `ETTerms.dll`,**不靠自身檔名**,改名後仍正常啟動。 ### 兩種發佈版本(兩個都要產出) | 版本 | 資料夾 | 指令旗標 | 需 .NET Runtime? | 用途 | |------|--------|----------|-------------------|------| | **A. 框架相依(預設)** | `ETTerms_v{Version}\` | `--self-contained false` | ✅ 需先裝 .NET 8 Desktop Runtime | 體積小;給已具備 runtime 的環境 | | **B. Portable(免安裝)** | `ETTerms_v{Version}_portable\` | `--self-contained true` | ❌ 不需要,runtime 已內含 | 體積大(約 240MB);給受 MIS 管控、不便裝 runtime 的環境,解壓即用、免系統管理員權限 | > ⚠️ **不要開 trimming**(`PublishTrimmed`):WinForms 大量用反射,trim 後易在執行時出錯。 **PowerShell 範例(一次產出兩種):** ```powershell $ver = ([regex]::Match((Get-Content src\ETTerms\ETTerms.csproj -Raw), '([^<]+)')).Groups[1].Value # A. 框架相依版 → ETTerms_v{Version}\ $root = "src\ETTerms\Publish\ETTerms_v$ver" if (Test-Path $root) { Remove-Item $root -Recurse -Force } dotnet publish src\ETTerms\ETTerms.csproj -c Release -r win-x64 --self-contained false -o $root Rename-Item (Join-Path $root "ETTerms.exe") "ETTerms v$ver.exe" # B. Portable 免安裝版 → ETTerms_v{Version}_portable\(runtime 已內含;MCP 也跟著 self-contained) $proot = "src\ETTerms\Publish\ETTerms_v${ver}_portable" if (Test-Path $proot) { Remove-Item $proot -Recurse -Force } dotnet publish src\ETTerms\ETTerms.csproj -c Release -r win-x64 --self-contained true -o $proot Rename-Item (Join-Path $proot "ETTerms.exe") "ETTerms v$ver.exe" ``` > 兩版的 `ETTerms.SerialMcp\` 與 `ETTerms.PduMcp\` 子資料夾都由 `PublishMcpServers` target 自動產生;portable 版的 MCP 也是 self-contained,故 AI MCP 功能在無 runtime 環境同樣可用。 --- ## Development Phases > 依使用者要求「**前面先把 GUI 做出來,後面再慢慢補功能**」排序: > Phase 1–2 先把 KKTerm 風格的視窗外殼、分頁、假連線清單做到「看得到、點得動」, > Phase 3 起才接真實 Serial / SSH channel,最後補腳本引擎與打包。 ### Phase 1 — 專案骨架 + UI Shell(工作量:S)✅ 已完成 **目標:** 專案能 `dotnet run` 啟動,KKTerm 風格的主視窗外殼可見:左側 Activity Rail、連線 Sidebar、右側分頁工作區(先放假分頁)。 **包含:** - [x] `dotnet new winforms` 建立 `src/ETTerms`,設定 `net8.0-windows` / `UseWindowsForms` / `Nullable` - [x] `MainForm.cs` + `MainForm.Designer.cs`:三欄佈局(Rail / Sidebar / Workspace) - [x] `ActivityRail.cs`:Terminal / Scripts / Settings 三個圖示按鈕(hover tooltip) - [x] `ConnectionSidebar.cs`:用假資料填 TreeView(SSH / Serial 圖示)+資料夾樹 / 搜尋 / CRUD / 拖曳分類(提前實作) - [x] `Workspace/WorkspaceView.cs`(取代原訂 `WorkspaceTabs.cs`):可平鋪 tiling 工作區(grid 預設 + 手動分割 + pane 內迷你分頁),提前實作 Future Extension 的分割畫面 - [x] `AppLogger.cs`:從 MyTeraTerm 移植日誌 - [x] 深色主題:`Theme.cs` + `NativeTheme.ApplyDarkTitleBar`(DWM 深色標題列)+ 深色對話框基底 **驗收條件:** ✅ `dotnet build` 通過(0 警告 0 錯誤);主視窗出現,可在 Rail 切檢視、Sidebar 看到假連線、可開關分頁與分割工作區。 ### Phase 2 — 連線資料 + Sidebar 真資料(工作量:M)✅ 已完成 **目標:** 連線清單改吃 SQLite 真資料,可新增 / 編輯 / 刪除連線,密碼存進 Credential Manager。 **包含:** - [x] `Connection.cs`:`Connection` / `SshSettings` / `SerialSettings` / `ConnectionType` / `SshAuthMethod` 模型 - [x] `ConnectionStore.cs`:SQLite CRUD(`%LocalAppData%\ETTerms\ettermsdb.sqlite` 自動建表,`SettingsJson` 存 Ssh/Serial) - [x] `CredentialVault.cs`:Windows Credential Manager 讀寫封裝(advapi32 P/Invoke,`CredWrite`/`CredRead`/`CredDelete`) - [x] 連線編輯對話框:依類型切換 SSH(host/port/user/auth/key/password)與 Serial(COM/baud/databits/parity/stopbits/handshake)兩種表單 - [x] Sidebar 綁定 `ConnectionStore`,支援新增 / 編輯 / 刪除 / 拖曳分組 / `SortOrder` 排序持久化(資料夾路徑存於 `GroupName`) **驗收條件:** ✅ 新增一條 SSH 與一條 Serial 連線、重啟 App 後仍在;密碼存在 Credential Manager(SQLite 內看不到明碼)。 > **實作備註:** 資料夾以連線的 `GroupName`(`/`-join 路徑)持久化;含連線的資料夾會在重啟後由路徑重建,**空資料夾為 session-only**(不另設 folders 表)。密碼一律經 `CredentialVault` 寫入 Credential Manager,SQLite 僅存 `CredentialKey`(`ETTerms/{Id}`)。 ### Phase 3 — Serial Port Session(工作量:M)✅ 已完成 **目標:** 雙擊 Serial 連線可開分頁、收發資料;先用最陽春的文字框驗證資料流。 **包含:** - [x] `Sessions/ISessionChannel.cs`:`Write(byte[])` + `event Action DataReceived` + `Open()` / `Close()`(: IDisposable) - [x] `Sessions/SerialChannel.cs`:`System.IO.Ports.SerialPort` 實作(開啟前向 SessionManager 占用 COM port、互斥、錯誤釋放) - [x] `Sessions/SessionPage.cs` + `Sessions/SessionManager.cs`:分頁內容(綁 channel)+ 全域 session 登錄 / COM 互斥 / 列舉 - [x] 暫用 `RichTextBox`(ReadOnly,靠遠端 echo 顯示)打通「鍵盤→送出、收到→顯示」 - [x] UI 跨執行緒安全:channel 在 handle 建立後才 Open,`DataReceived` 經 `BeginInvoke` 回 UI thread - [x] 連線資料流改打通:`ConnectionActivated` 改傳完整 `Connection`,`WorkspaceView.BuildPage` 依類型建 `SessionPage`(Serial) 或佔位(SSH);關分頁 / 關 pane 經 `SessionPage.Dispose` 釋放 COM port **驗收條件:** ✅ 編譯通過(0 警告 0 錯誤)。接一塊開發板 / com0com loopback,打字送得出去、回傳看得到,關分頁能正確釋放 COM port(需實機 `dotnet run` 驗證硬體收發)。 ### Phase 4 — SSH Session(工作量:M)✅ 已完成 **目標:** 雙擊 SSH 連線可登入遠端、互動式 shell 可用。 **包含:** - [x] `Sessions/SshChannel.cs`:SSH.NET `SshClient` + `ShellStream`(背景執行緒 Connect 避免凍 UI),實作 `ISessionChannel` - [x] 三種認證:password / private key(passphrase 走 `CredentialVault`)/ keyboard-interactive - [x] Host key 指紋 TOFU:`Sessions/HostKeyStore.cs`(`known_hosts.txt`)首次記錄 SHA256、之後比對、不符中止並警告 - [x] 連線錯誤(逾時 / 認證失敗 / 斷線)寫入終端機輸出 + `AppLogger` - [x] PTY resize:反射呼叫 ShellStream 底層 `_channel.SendWindowChangeRequest`(SSH.NET 未公開此 API) **驗收條件:** ✅ 編譯通過(0/0)。用 password 與 key 兩種方式各登入一台 SSH 主機,能跑 `ls` / `top` 等互動命令(需實機驗證)。 > **套件:** SSH.NET 2024.2.0(含 BouncyCastle.Cryptography 2.4.0)。 ### Phase 5 — VT100 終端機控制項(工作量:L)✅ 已完成 **目標:** 用自繪 VT100 控制項取代陽春文字框,正確顯示顏色 / 游標 / 清屏等 ANSI 行為。 **包含:** - [x] `Terminal/ScreenBuffer.cs`:rows×cols 字格(`Cell{Ch,Fg,Bg,Attr}`,含 Bold/Underline/Inverse)+ scrollback + 滾動區(DECSTBM)+ alt screen - [x] `Terminal/AnsiParser.cs`:狀態機(Ground/Esc/CSI/OSC)—游標移動、SGR(16 色 / 256 色 / truecolor)、清屏 / 清行、scroll region、insert/delete line/char、alt screen(47/1047/1049)、DECTCEM(?25)/DECAWM(?7)/DECCKM(?1)+ `Palette` - [x] `Terminal/TerminalView.cs`:owner-drawn 雙緩衝、run 合併繪製、滾輪 scrollback、選取 / 複製(Ctrl+C、右鍵)/ 貼上(Ctrl+V、Shift+Insert、右鍵) - [x] `Terminal/TerminalInput.cs`:鍵盤 → byte 序列(方向鍵含 appCursor、Fn、Home/End/PgUp/PgDn、Enter/Tab/Backspace/Esc) - [x] 套用 `Terminal/TerminalProfile`(字型 / 字級 / scrollback 行數);resize 由 `OnSizeChanged` 算 cols/rows 並 `Resized` 事件通知 channel(PTY size) **驗收條件:** ✅ 編譯通過(0/0)。在 SSH 跑 `vim` / `htop` / `top` 顏色與版面正常、視窗 resize 通知遠端(需實機驗證)。 ### Phase 6 — TTL 腳本引擎移植 + 執行 UI(工作量:L) **目標:** 移植 MyTeraTerm 的 `TTLInterpreter`,改為驅動 `ISessionChannel`,可對 active session 跑 `.ttl` 腳本。 **包含:** - [x] `TTLInterpreter.cs`:從 MyTeraTerm 移植,建構子改吃 `ISessionChannel`(取代 `ComPortBridge`) - [x] 指令覆蓋:`send` / `sendln` / `pause` / `wait` / `timeout` / `flushrecv` / `logopen` / `logwrite` / `logclose` / `messagebox` / `if`-`elseif`-`else`-`endif` / `while`-`endwhile` / 變數指派 - [x] `ScriptRunner.cs`:`async` 執行 + 取消(`Cancel()`)+ `StatusChanged`(檔名 / 行號 / 指令)事件 - [x] Group 同步指令:`waitall` / `sendlnall` / `sendlngroup` + `GroupSyncContext`(Barrier 同步) - [x] `▶ Run All`(對所有 Serial 個別跑)+ `▶ Group1/2/3`(Group 同步跑) - [x] `▶ Script` / `▶ Run All` 拒絕含 Group 指令的腳本(彈 Warning) - [x] `docs/ttl-script-reference.md`:指令對照表(含 Group 指令) - [ ] SSH session 完整驗收(SSH 自動登入 + 下命令 + `logopen` 收 log) **驗收條件:** Serial 已驗收通過。SSH 尚待實機驗證:載入一支 `.ttl`(SSH 自動登入 + 下命令 + `logopen` 收 log),對 active session 跑完並產生 log 檔,中途可按停止中止。 ### Phase 7 — Settings + About 頁面(工作量:S)✅ 已完成 **目標:** Activity Rail 加入 Settings / About 獨立頁面,提供偏好設定入口與版本紀錄。 **包含:** - [x] `ActivityRail` 新增 Settings / About 兩個 view(三圖示:▤ / ⚙ / ℹ) - [x] `MainForm` view 切換邏輯(Terminal / Settings / About 互斥顯示) - [x] `SettingsView.cs`:偏好設定頁面骨架(Phase 8 擴充內容) - [x] `AboutView.cs`:左側 App 資訊 + Developer + Tech Stack 卡片,右側 Changelog timeline - [x] Changelog 資料結構,新版本只需加一筆 `ChangelogEntry` **驗收條件:** ✅ 點 Activity Rail 可切換三個頁面;About 頁正確顯示版本、作者、changelog。 ### Phase 8 — PDU 控制 + Settings 擴充 + Shell + SFTP(工作量:M)✅ 已完成 **目標:** 補上 SNMP PDU 控制、偏好設定持久化、本機 Shell(ConPTY)、SFTP 瀏覽器。 **包含:** - [x] `Scripting/Pdu/PduController.cs`:SnmpSharpNet 實作,接 `TTLInterpreter` 的 `pductrl` / `pduconnect` - [x] `Infrastructure/AppSettings.cs`:終端機偏好 / Shell 設定 / 視窗位置記憶(JSON 存 `%LocalAppData%\ETTerms\`) - [x] Settings 檢視 UI:Terminal 分頁(字型 / 配色 / scrollback / 預設換行 / Shell 設定 + 即時預覽)+ PDU 分頁(連線 / 狀態監控) - [x] `Sessions/ShellChannel.cs`:Windows ConPTY 本機 Shell(PowerShell / Bash / Cmd),完整 PTY 支援 - [x] Sidebar SFTP 分頁:SSH SFTP 檔案瀏覽器(連線 / 導航 / 目錄列表) - [x] `docs/runbooks/troubleshooting.md`:常見問題(COM / SSH / TTL / PDU) - [ ] `dotnet publish` + Inno Setup / MSIX 安裝程式(待使用者指示) **驗收條件:** ✅ PDU 指令可用;Settings 重啟保留;Local Shell 行為正常(ConPTY);SFTP 可瀏覽遠端目錄。打包待後續。 ### Phase 9 — AI 整合:Serial MCP Server(GUI 橋接模式)(工作量:M)✅ 已完成 **目標:** 讓 Kiro CLI / Claude CLI 等 AI agent 收發 serial,且**使用者能在 GUI 即時看到 AI 的收發**。COM port 由 GUI 唯一持有,MCP 經本機 named pipe 橋接(見 [AI / MCP Integration](#ai--mcp-integrationserial-mcp-server))。 **包含:** - [x] GUI:`Sessions/SerialBridgeServer.cs`——本機 named pipe server(`\\.\pipe\etterms-serial`),暴露 list / attach / write / read,並把 serial session 的 RX 廣播給 client;`SerialBridge.cs` 為單一 session 的橋接端點 - [x] GUI:`SessionPage.WriteFromAi` 把 MCP 來源的 TX 以 `[AI]` 標色(`\x1b[35m`)echo,使用者可區分 AI 與手打輸入 - [x] `src/ETTerms.SerialMcp/`:.NET 8 console + 官方 C# MCP SDK(`ModelContextProtocol`),stdio / JSON-RPC - [x] `SerialBridgeClient.cs`:連 GUI pipe、轉發 write、背景累積 RX - [x] 工具:`serial_list` / `serial_attach` / `serial_write` / `serial_read`(含 `waitFor` + `timeoutMs`)/ `serial_detach` - [x] 註冊說明(`kiro-cli mcp add` / agent.json `mcpServers`)寫入 [docs/serial-mcp-guide.md](docs/serial-mcp-guide.md),含「需先在 GUI 開好 port」前提 **驗收條件:** ✅ GUI 開一條 Serial(COM3)→ 另一分頁 PowerShell 跑 kiro → AI 經 MCP `serial_attach` COM3 → `serial_write` 送指令、`serial_read` 讀回應,**整個過程在 GUI Tab1 即時可見(AI 的 TX 有 `[AI]` 標色)**;全程只有 GUI 開該 port。 ### Phase 10 — 內建 AI Assistant(BYO endpoint agent)(工作量:M)✅ 已完成(v0.6.0) **目標:** 不依賴 Claude / Kiro,GUI 內建 AI 聊天檢視,自然語言驅動 serial + PDU;**發佈版不含任何私人端點**(設計見 [內建 AI Assistant](#內建-ai-assistantphase-10--✅-已完成-v060))。 **包含:** - [x] `AppSettings` 加 `AiBaseUrl` / `AiModel` / `AiSystemPrompt`(預設空白);API key 存 `CredentialVault`(`ETTerms/AiApiKey`) - [x] `App/SettingsView.cs` 加 **AI Assistant** 分頁:Base URL / Model / API Key(密碼框)/ 系統提示詞,Save 寫 settings + Credential Manager;空白=停用 - [x] `App/AiChatView.cs`:聊天 UI(乾淨逐字稿:user 靠右 accent、AI 靠左、工具灰字、thinking 收進 Send 按鈕)+ 底部控制列(輸入框 Fill + 右下角模型下拉 / Send);`RefreshProvider()` 開分頁時依設定重建 client。**作為 Workspace pane**:`WorkspaceView` 工具列 `✨ AI Chat` → `OpenAiPane()`,`Session` 抽象化容納 `SessionPage` 或 `AiChatView`(`Content` 屬性),可與 serial 用 Layout 並排;AI 分頁無 Group / Log / Script(自動 skip)。**模型下拉**打端點 `/v1/models` 列出可選模型、即時切換並記住(Settings 不再設 model,只留 Base URL / Key / 系統提示詞) - [x] `Ai/OpenAiChatClient.cs`:極簡 OpenAI 相容 chat-completions(HttpClient,非串流) - [x] `Ai/AgentHost.cs`:手寫 agent loop(tool_calls → 執行 → role=tool 餵回 → 迴圈,上限 8 輪) - [x] `Ai/AiTools.cs`:serial(list/attach/write/read,經 `SerialBridge`,`[AI]` echo 沿用)+ PDU(connect/status/set_port/power_cycle,`ETTerms.PduCore`);破壞性動作經 `ConfirmAsync` 彈框;每筆呼叫寫 AppLogger - [x] AI 入口在 `WorkspaceView` 工具列(`✨ AI Chat` 按鈕),開成可並排的 pane(非獨立 rail view)——這樣才能與 serial 分頁同時使用 - [ ] (延伸,未做)MCP host:讓使用者掛自己的第三方 MCP server(schema concat 進 `AiTools.GetSchemas()`) **驗收條件:** ✅ 建置 0 錯誤;未設定 Provider 時 AI 面板停用並提示;`src` grep 無任何私人端點 / key(範例一律 localhost / 假 IP)。實機端到端(接 OpenAI 相容端點跑 serial/PDU 一句話流程、PDU 確認框、`[AI]` 標色、AppLogger 紀錄)待使用者驗收。 --- ## Future Extensions 這個版本**不做、但未來可能加**: - ~~**AI / MCP 整合**~~(✅ 已於 [Phase 9](#development-phases) 實作:Serial MCP Server,讓 AI agent 直接操作 serial;未來可再擴充 SSH / Shell MCP 工具) - ~~**SFTP 檔案瀏覽**~~(✅ 已於 Phase 8 實作:sidebar SFTP 分頁) - ~~**AI Chat 氣泡版(WebView2)**~~(✅ v0.7.0 已實作):AI 分頁訊息區改用 WebView2 渲染真氣泡(user 右 / AI 左)+ Markdown(Markdig 轉 HTML:程式碼區塊、表格、清單)+ 送出後 thinking 動畫泡。HTML 模板 `Ai/ChatHtml.cs`(全內嵌 CSS/JS,`NavigateToString`),C# 經 `ExecuteScriptAsync` 呼叫 JS(`addUser`/`addAI`/`addTool`/`showThinking`/`hideThinking`…;WebView2 未就緒前的呼叫先入佇列,`NavigationCompleted` 後 flush)。WebView2 使用者資料夾 `%LocalAppData%\ETTerms\WebView2`。底部控制列(輸入框 / 模型下拉 / Send)仍 WinForms。依賴 WebView2 Runtime(Win11 內建)。 - **Telnet** session 類型(補一個 `TelnetChannel : ISessionChannel`) - **RDP / VNC** 分頁(KKTerm 用 mstscax.dll;ETTerms 可後期評估) - **tmux 自動 attach**(SSH 斷線後自動回貼,仿 KKTerm) - ~~分割畫面 / 多 pane~~(✅ 已於 Phase 1 提前實作:`WorkspaceView` + `PaneControl` + `ProportionalSplit`) - **拖曳 pane 重新排列**(目前可分割 / 關閉 / 調大小,但還不能把 pane 拖到別處;未來可加 drag-drop 重排) - **佈局記憶**(記住上次的 grid / split 佈局,重啟還原) - **全新腳本語言或內嵌 Lua / C# scripting**(目前先沿用 TTL;未來可加第二引擎) - **連線分組 / 標籤 / 搜尋** 強化 - **跨平台**(WinForms 綁 Windows;若要跨平台需評估 Avalonia / MAUI 重寫 UI 層,但 `Sessions` / `Scripting` 層因抽象良好可重用) - **macOS / Linux 終端機渲染** 改用 WebView2 + xterm.js(若要與 KKTerm 視覺對齊) --- ## Reference Projects(`For_AI/`) | 專案 | 角色 | 取用重點 | |------|------|---------| | `KKTerm-main` | **UI 參考** | Activity Rail + 分頁工作區 + Saved Connections sidebar 的版面與互動;SQLite 存連線、Credential Manager 存密碼的 local-first 思路 | | `MyTeraTerm` | **Script 參考** | `lib/TTLInterpreter.cs` 的 TTL 指令實作(直接移植)、`AppLogger.cs` 日誌、PDU/SNMP 控制(`pductrl`/`pduconnect`);`ComPortBridge.cs` 的 serial 經驗 | > 注意:KKTerm 是 Tauri/React,**不**直接複製程式碼,只取 UI/UX 與資料架構概念;ETTerms 是純 WinForms。MyTeraTerm 的 `TTLInterpreter` 則可大段移植,主要改建構子從 `ComPortBridge` 換成 `ISessionChannel`。