PublishSerialMcp target now inherits the GUI's SelfContained setting, so a --self-contained true publish also produces a runtime-free ETTerms.SerialMcp. Document both publish variants in ARCHITECTURE.md / CLAUDE.md: framework-dependent ETTerms_v{Version}\ and portable ETTerms_v{Version}_portable\ (no .NET runtime install needed).
47 KiB
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) |
獨立行程,但不自己開 port——經本機 named pipe 橋接到 GUI 持有的 serial session,把收發暴露成 AI 可呼叫工具(Kiro CLI / Claude CLI),見 AI / MCP Integration |
| 打包 | 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/Scripts/Settings)
│ ├── ConnectionSidebar.cs# 仿 KKTerm 可編輯資料夾樹(搜尋/CRUD/拖曳分類)
│ ├── 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)
│ ├── AnsiParser.cs # ANSI/VT100 escape 解析狀態機
│ ├── 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
│ ├── 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)
│
└── 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。
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。
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);保留至少一格
- Grid 預設:工具列一鍵切
- 每個 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)
Script Editor / Runner(Scripts 檢視)
- 載入 / 編輯
.ttl腳本(語法沿用 MyTeraTerm) - 對「目前 active session」執行腳本;顯示執行進度(檔名 / 行號 / 當前指令)
- 可取消執行(
Cancel());logopen將輸出寫檔 sprintf2:Cprintf風格格式化輸出到變數(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 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 → GUISerialChannel.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.exe>\ETTerms.SerialMcp\ETTerms.SerialMcp.exe(與 publish 慣例對齊,必定存在);卡片同時顯示 CLI 驗證指令,按 Remove 可移除。
方式 B — 手動 CLI:
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 → 送指令看回應」。
Key Constraints & Business Rules
- 單機、無雲: 所有連線 metadata 存本機 SQLite,密碼存 Windows Credential Manager,不回傳任何遙測。
- 密碼絕不落地明碼: SQLite 只存
CredentialKey,實際密碼 / passphrase 一律走 Credential Manager。 - 一條連線一個分頁: 同一連線可開多個分頁(各自獨立 session),但每個分頁綁定一個
ISessionChannel。 - 腳本對 active session 執行: TTL 腳本只作用在目前選定的分頁 channel,不會跨分頁亂送。例外:Group 模式下
waitall/sendlnall/sendlngroup可跨同 Group 成員同步。 - 腳本可取消: 長時間
wait/while必須能被使用者中止(isCancelled旗標 +OperationCanceledException)。 - Serial port 互斥: 一個 COM port 同時只能被一個 session 開啟;開啟前需檢查可用性。
- VT 相容性以常見情境為準: VT100 / 常見 ANSI 序列優先;冷門 escape 可後補,不阻塞 GUI 進度。
- GUI 先行: Phase 1–2 必須先讓視窗外殼 + 分頁 + 假連線可見可操作,再接真實 channel。
- 不依賴外部 exe: 不嵌 TeraTerm、不需 com0com;全原生 .NET 元件。
- UI 不可被 channel I/O 阻塞: channel 讀寫在背景,UI 更新一律
Invoke回 UI thread。
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。
- 輸入處理: 終端機輸入直接透傳給遠端,不做 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
# 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(手動確認)
# <TargetFramework>net8.0-windows</TargetFramework>
# <UseWindowsForms>true</UseWindowsForms>
# <Nullable>enable</Nullable>
# 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<Version>
# 後續可用 Inno Setup / MSIX 做安裝程式
Publish / 打包慣例
每次發 Release 都依此規則產出,方便辨識版本與一併攜帶 MCP server。
輸出位置(固定): src\ETTerms\Publish\ETTerms_v{Version}\
{Version}取自src\ETTerms\ETTerms.csproj的<Version>(例:0.2.0)。- 資料夾命名:
ETTerms_v{Version}(前綴v,例:ETTerms_v0.2.0)。 Publish/已 gitignore(publish/),不入 git。
內容結構:
ETTerms_v0.2.0\
├── ETTerms v0.2.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 + 相依
規則:
- GUI publish 會自動帶上 MCP:
ETTerms.csproj有PublishSerialMcptarget(AfterTargets="Publish"),會把ETTerms.SerialMcp一併發佈到<publish>\ETTerms.SerialMcp\子資料夾(與 GUI 相依 dll 隔離)。因此只要發佈 GUI 一個指令即可,不必再單獨發 MCP。- 對齊
McpRegistrar.ResolveServerExe():它解析的<ETTerms.exe>\ETTerms.SerialMcp\ETTerms.SerialMcp.exe因此必定存在,AI MCP 一鍵設定寫進去的路徑才不會落空。 - MCP 子發佈會跟隨 GUI 的
SelfContained設定(target 內以$(SelfContained)傳入):框架相依版的 MCP 也框架相依;portable 版的 MCP 也免 runtime。
- 對齊
- 主 exe 改名:
dotnet publish產生的ETTerms.exe重新命名為ETTerms v{Version}.exe。- 可安全改名:.NET apphost 內部記錄要載入的
ETTerms.dll,不靠自身檔名,改名後仍正常啟動。
- 可安全改名:.NET apphost 內部記錄要載入的
兩種發佈版本(兩個都要產出)
| 版本 | 資料夾 | 指令旗標 | 需 .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 範例(一次產出兩種):
$ver = ([regex]::Match((Get-Content src\ETTerms\ETTerms.csproj -Raw), '<Version>([^<]+)</Version>')).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\子資料夾都由PublishSerialMcptarget 自動產生;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、右側分頁工作區(先放假分頁)。
包含:
dotnet new winforms建立src/ETTerms,設定net8.0-windows/UseWindowsForms/NullableMainForm.cs+MainForm.Designer.cs:三欄佈局(Rail / Sidebar / Workspace)ActivityRail.cs:Terminal / Scripts / Settings 三個圖示按鈕(hover tooltip)ConnectionSidebar.cs:用假資料填 TreeView(SSH / Serial 圖示)+資料夾樹 / 搜尋 / CRUD / 拖曳分類(提前實作)Workspace/WorkspaceView.cs(取代原訂WorkspaceTabs.cs):可平鋪 tiling 工作區(grid 預設 + 手動分割 + pane 內迷你分頁),提前實作 Future Extension 的分割畫面AppLogger.cs:從 MyTeraTerm 移植日誌- 深色主題:
Theme.cs+NativeTheme.ApplyDarkTitleBar(DWM 深色標題列)+ 深色對話框基底 驗收條件: ✅dotnet build通過(0 警告 0 錯誤);主視窗出現,可在 Rail 切檢視、Sidebar 看到假連線、可開關分頁與分割工作區。
Phase 2 — 連線資料 + Sidebar 真資料(工作量:M)✅ 已完成
目標: 連線清單改吃 SQLite 真資料,可新增 / 編輯 / 刪除連線,密碼存進 Credential Manager。 包含:
Connection.cs:Connection/SshSettings/SerialSettings/ConnectionType/SshAuthMethod模型ConnectionStore.cs:SQLite CRUD(%LocalAppData%\ETTerms\ettermsdb.sqlite自動建表,SettingsJson存 Ssh/Serial)CredentialVault.cs:Windows Credential Manager 讀寫封裝(advapi32 P/Invoke,CredWrite/CredRead/CredDelete)- 連線編輯對話框:依類型切換 SSH(host/port/user/auth/key/password)與 Serial(COM/baud/databits/parity/stopbits/handshake)兩種表單
- 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 連線可開分頁、收發資料;先用最陽春的文字框驗證資料流。 包含:
Sessions/ISessionChannel.cs:Write(byte[])+event Action<byte[]> DataReceived+Open()/Close()(: IDisposable)Sessions/SerialChannel.cs:System.IO.Ports.SerialPort實作(開啟前向 SessionManager 占用 COM port、互斥、錯誤釋放)Sessions/SessionPage.cs+Sessions/SessionManager.cs:分頁內容(綁 channel)+ 全域 session 登錄 / COM 互斥 / 列舉- 暫用
RichTextBox(ReadOnly,靠遠端 echo 顯示)打通「鍵盤→送出、收到→顯示」 - UI 跨執行緒安全:channel 在 handle 建立後才 Open,
DataReceived經BeginInvoke回 UI thread - 連線資料流改打通:
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 可用。 包含:
Sessions/SshChannel.cs:SSH.NETSshClient+ShellStream(背景執行緒 Connect 避免凍 UI),實作ISessionChannel- 三種認證:password / private key(passphrase 走
CredentialVault)/ keyboard-interactive - Host key 指紋 TOFU:
Sessions/HostKeyStore.cs(known_hosts.txt)首次記錄 SHA256、之後比對、不符中止並警告 - 連線錯誤(逾時 / 認證失敗 / 斷線)寫入終端機輸出 +
AppLogger - 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 行為。 包含:
Terminal/ScreenBuffer.cs:rows×cols 字格(Cell{Ch,Fg,Bg,Attr},含 Bold/Underline/Inverse)+ scrollback + 滾動區(DECSTBM)+ alt screenTerminal/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)+PaletteTerminal/TerminalView.cs:owner-drawn 雙緩衝、run 合併繪製、滾輪 scrollback、選取 / 複製(Ctrl+C、右鍵)/ 貼上(Ctrl+V、Shift+Insert、右鍵)Terminal/TerminalInput.cs:鍵盤 → byte 序列(方向鍵含 appCursor、Fn、Home/End/PgUp/PgDn、Enter/Tab/Backspace/Esc)- 套用
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 腳本。
包含:
TTLInterpreter.cs:從 MyTeraTerm 移植,建構子改吃ISessionChannel(取代ComPortBridge)- 指令覆蓋:
send/sendln/pause/wait/timeout/flushrecv/logopen/logwrite/logclose/messagebox/if-elseif-else-endif/while-endwhile/ 變數指派 ScriptRunner.cs:async執行 + 取消(Cancel())+StatusChanged(檔名 / 行號 / 指令)事件- Group 同步指令:
waitall/sendlnall/sendlngroup+GroupSyncContext(Barrier 同步) ▶ Run All(對所有 Serial 個別跑)+▶ Group1/2/3(Group 同步跑)▶ Script/▶ Run All拒絕含 Group 指令的腳本(彈 Warning)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 獨立頁面,提供偏好設定入口與版本紀錄。 包含:
ActivityRail新增 Settings / About 兩個 view(三圖示:▤ / ⚙ / ℹ)MainFormview 切換邏輯(Terminal / Settings / About 互斥顯示)SettingsView.cs:偏好設定頁面骨架(Phase 8 擴充內容)AboutView.cs:左側 App 資訊 + Developer + Tech Stack 卡片,右側 Changelog timeline- Changelog 資料結構,新版本只需加一筆
ChangelogEntry驗收條件: ✅ 點 Activity Rail 可切換三個頁面;About 頁正確顯示版本、作者、changelog。
Phase 8 — PDU 控制 + Settings 擴充 + Shell + SFTP(工作量:M)✅ 已完成
目標: 補上 SNMP PDU 控制、偏好設定持久化、本機 Shell(ConPTY)、SFTP 瀏覽器。 包含:
Scripting/Pdu/PduController.cs:SnmpSharpNet 實作,接TTLInterpreter的pductrl/pduconnectInfrastructure/AppSettings.cs:終端機偏好 / Shell 設定 / 視窗位置記憶(JSON 存%LocalAppData%\ETTerms\)- Settings 檢視 UI:Terminal 分頁(字型 / 配色 / scrollback / 預設換行 / Shell 設定 + 即時預覽)+ PDU 分頁(連線 / 狀態監控)
Sessions/ShellChannel.cs:Windows ConPTY 本機 Shell(PowerShell / Bash / Cmd),完整 PTY 支援- Sidebar SFTP 分頁:SSH SFTP 檔案瀏覽器(連線 / 導航 / 目錄列表)
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)。 包含:
- GUI:
Sessions/SerialBridgeServer.cs——本機 named pipe server(\\.\pipe\etterms-serial),暴露 list / attach / write / read,並把 serial session 的 RX 廣播給 client;SerialBridge.cs為單一 session 的橋接端點 - GUI:
SessionPage.WriteFromAi把 MCP 來源的 TX 以[AI]標色(\x1b[35m)echo,使用者可區分 AI 與手打輸入 src/ETTerms.SerialMcp/:.NET 8 console + 官方 C# MCP SDK(ModelContextProtocol),stdio / JSON-RPCSerialBridgeClient.cs:連 GUI pipe、轉發 write、背景累積 RX- 工具:
serial_list/serial_attach/serial_write/serial_read(含waitFor+timeoutMs)/serial_detach - 註冊說明(
kiro-cli mcp add/ agent.jsonmcpServers)寫入 docs/serial-mcp-guide.md,含「需先在 GUI 開好 port」前提 驗收條件: ✅ GUI 開一條 Serial(COM3)→ 另一分頁 PowerShell 跑 kiro → AI 經 MCPserial_attachCOM3 →serial_write送指令、serial_read讀回應,整個過程在 GUI Tab1 即時可見(AI 的 TX 有[AI]標色);全程只有 GUI 開該 port。
Future Extensions
這個版本不做、但未來可能加:
AI / MCP 整合(✅ 已於 Phase 9 實作:Serial MCP Server,讓 AI agent 直接操作 serial;未來可再擴充 SSH / Shell MCP 工具)SFTP 檔案瀏覽(✅ 已於 Phase 8 實作:sidebar SFTP 分頁)- 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。