Terminal: - cache font variants + single reusable brush in render hot path; resolve cell colors once per cell; dispose GDI resources - scrollback: List+RemoveRange -> O(1) ring buffer - follow new output only when already at bottom (no more yank-to-bottom while reading history) - alt-screen mouse wheel -> arrow keys (vim/htop scroll) - answer DSR (ESC[5n/6n) and DA (ESC[c) queries so TUIs no longer hang - remove dead code in OnKeyPress Encoding correctness (garbled CJK across chunk boundaries): - stateful UTF-8 Decoder in TTLInterpreter.OnData, SerialBridgeServer rx forwarding, and SessionLogger.Write Sessions: - ShellChannel: free proc-thread attribute list, close hProcess, notify '[ETTerms] shell process exited' in the tab - SshChannel: surface ErrorOccurred / ShellStream.Closed in the tab; Write no longer throws into the UI thread on a dead connection PDU: - new shared ETTerms.PduCore project replaces the two drifted copies of PduController (GUI + PduMcp); logging via injected delegates - batched SNMP GET (GetAllPortsStatus): 12-port poll is 3 UDP round-trips instead of 36 (StatusView polling + pdu_status tool) Scripting: - cap TTL receive buffer at 1MB; skip re-scan in wait when buffer length unchanged - merge ScriptRunner.RunAsync/RunGroupAsync; new TtlScript helper dedups script picking + group-command checks (3 copies -> 1) Misc: - ConnectionStore: parse LastUsedUtc with InvariantCulture/RoundtripKind - version 0.4.0; About changelog; CLAUDE.md notes (intentional group barrier behavior, SSH.NET reflection resize caveat) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 KiB
CLAUDE.md — ETTerms
給 Claude 的專案記憶與指令入口。詳細架構見 ARCHITECTURE.md。
專案簡介
ETTerms 是一個 C# .NET 8 WinForms 的原生 Windows 終端機工作台,主打 SSH 與 Serial Port 兩種連線,並沿用 / 擴充 MyTeraTerm 的 TTL 腳本引擎做自動化。UI 參考 KKTerm(Activity Rail + 分頁工作區 + Saved Connections sidebar)。單機、無雲、無登入系統。
開發策略:GUI 先行 — 先把視窗外殼 + 分頁 + 連線清單做出來,再逐步補 Serial → SSH → VT100 → 腳本引擎 → Settings/About → PDU/Shell/SFTP。
進度: Phase 1–5 ✅、Phase 6 ✅(TTL 引擎 + Group 同步,SSH 待驗收)、Phase 7 ✅(Settings/About)、Phase 8 ✅(PDU + Shell/ConPTY + SFTP + Settings 擴充)、Phase 9 ✅(Serial MCP server:GUI 持有 COM port,MCP 經本機 named pipe 橋接,AI 收發的資料即時以 [AI] 標色顯示在 GUI)。打包待指示。
v0.4.0: 效能 / 穩定性總整理(來自全 codebase 審視)。(1) 終端機繪製熱路徑去配置 — TerminalView 快取 4 種 Font 變體(Bold/Underline 組合)與單一可變色 SolidBrush,run 合併時每 cell 顏色只解析一次,Dispose 收掉 GDI 資源;(2) scrollback 改環形緩衝 — ScreenBuffer 從 List+RemoveRange(0,…)(滿了每行 O(n) 搬移)改 O(1) ring buffer;(3) UTF-8 跨 chunk 亂碼修正(3 處) — TTLInterpreter.OnData、SerialBridgeServer rx 轉發、SessionLogger.Write 改用 stateful Decoder(比照 AnsiParser 原本的正確做法);(4) PDU SNMP 批次查詢 — 新共用專案 src/ETTerms.PduCore/ 取代 GUI 與 PduMcp 兩份複製的 PduController(log 走建構子委派),新增 GetAllPortsStatus() 一個 SNMP GET 帶 12 個 varbind,12 port 輪詢從 36 個 UDP 來回縮成 3 個(StatusView 與 pdu_status 都改用);(5) ShellChannel 資源修正 — DeleteProcThreadAttributeList+FreeHGlobal 釋放 attribute list、CloseHandle(pi.hProcess),shell 自行 exit 時顯示灰色 [ETTerms] shell process exited 提示;(6) SSH 斷線可見 — 訂 ErrorOccurred/ShellStream.Closed 顯示提示,Write 例外不再炸 UI thread;(7) 終端機行為 — 新輸出只在已貼底時跟隨(看歷史不被拉回底部)、alt screen 滾輪轉方向鍵(vim/htop 可滾)、回應 DSR ESC[6n/ESC[5n 與 DA ESC[c(TUI 查游標位置不再卡住);(8) TTL — _recv 上限 1MB、wait 輪詢長度沒變不重掃;(9) 去重複 — ScriptRunner 兩個 Run 合併、TtlScript 共用選檔/group 指令檢查(SessionPage/WorkspaceView 三份流程收斂)、ConnectionStore 讀取改 InvariantCulture+RoundtripKind。註:group waitall 一個成員失敗其他成員停在 barrier 是刻意行為(使用者要求整組停下),勿「修」。
v0.3.2: (1) 新增左側 Status rail view — 位於 Terminal 與 Settings 之間(圖示 ⚡,新增 ActivityRail.RailView.Status 列舉與 Items),獨立 App/StatusView.cs,沿用 SettingsView 的自繪 tab strip + panel 切換風格(避免 TabControl 白邊);MainForm 加 _statusView 欄位、佈局與 rail 切換可見性。目前只有 PDU 分頁,未來會再加分頁(MakeTab(...) 即可擴充)。(2) PDU 搬到 Status 並改自動輪詢 — PDU 從 SettingsView 移到 StatusView,移除手動 Refresh 按鈕,連線成功後以 System.Threading.Timer(period 3000ms)在背景執行緒讀 SNMP、再 BeginInvoke 回 UI 更新表格(不卡 UI);Interlocked 旗標防止前一輪未讀完就重入;斷線 / 控制項 Dispose 時自動停掉 timer 與 PduController;表格下方顯示「last update HH:mm:ss」。SettingsView 移除 BuildPduTab/RefreshPduGrid 與 ETTerms.Scripting.Pdu import,現只剩 Terminal / AI MCP 兩分頁。(2b) PDU 分頁加手動開關鈕 — 狀態表格新增 Control 欄(DataGridViewButtonColumn),每個 Port 一顆鈕,文字隨狀態切換(ON→「Turn OFF」、OFF→「Turn ON」、未知→「—」);點擊在背景執行緒呼叫 PduController.SetPortOn/SetPortOff(不卡 UI),等 ~400ms 後回讀刷新整表,失敗跳 MessageBox;未連線點擊會提示先 Connect,斷線時清空表格避免顯示過時狀態。(3) 修 ShellChannel 啟動目錄 fallback — 本機 Shell 的 StartupDirectory 若已不存在(外接碟拔除 / 資料夾被刪)會 CreateProcess failed: 267 (ERROR_DIRECTORY);改為「為空 或 Directory.Exists 為 false」即 fallback 到使用者家目錄(Environment.SpecialFolder.UserProfile,隨登入者變動,例如 C:\Users\et_wen),與 Windows PowerShell 行為一致。
v0.3.1: 終端機體驗修正(皆在 src/ETTerms/Terminal/)。(1) 深色垂直捲軸 — 新增自繪 DarkScrollBar(細長、無箭頭、圓角滑塊,配合 KKTerm 深色主題),TerminalView 右側 Dock=Right 掛上,ContentWidth 扣掉捲軸寬避免文字被蓋,UpdateScrollBar() 在 Feed / 滾輪 / resize 時同步滑塊範圍與位置,滾輪與拖曳互通。(2) 多行貼上修正 — AnsiParser 新增 BracketedPaste(DEC mode 2004);TerminalView.Paste() 在對方啟用 bracketed paste(PSReadLine / Kiro CLI 等)時以 ESC[200~ … ESC[201~ 包夾整段,視為「單次貼上」而非逐行 Enter 立即送出;未啟用時退回原本逐字送出。(3) 右鍵複製清反白 — 右鍵複製後清掉 _hasSel 並重繪,讓使用者知道已複製。
v0.3.0: 新增 ETTerms.PduMcp(stdio MCP server):讓 AI agent 直接控制 SNMP PDU 插座。與 serial 不同,PDU 走 SNMP(UDP) 非獨佔,故 PduMcp 直接打 SNMP、不經 GUI 橋接(內含精簡版 PduController,OID 邏輯複製自 GUI 版,log 走 stderr),GUI 不開著也能用。工具:pdu_connect / pdu_list / pdu_set_port / pdu_get_port / pdu_status / pdu_power_cycle / pdu_disconnect,回傳統一 {ok, result/error} JSON;連線狀態以行程內單例 PduRegistry(IP→controller)保存。McpRegistrar 改為多 server,Settings → AI MCP 一鍵同時註冊 etterms-serial 與 etterms-pdu;ETTerms.csproj 的 publish target 更名 PublishMcpServers,GUI publish 會把兩個 MCP 各自帶到 \ETTerms.SerialMcp\、\ETTerms.PduMcp\ 子資料夾。
v0.2.1: 新增 GUI Settings → AI MCP 分頁(McpRegistrar):對 Claude Code(~/.claude.json)與 Kiro(~/.kiro/settings/mcp.json)一鍵 Setup / Remove 註冊 etterms-serial MCP server,read-modify-write 保留檔內其他設定、原子寫回;卡片附 CLI 驗證指令。ETTerms.csproj 加 publish target(AfterTargets=Publish),GUI publish 會自動把 MCP server 帶到子資料夾,與 McpRegistrar.ResolveServerExe() 解析路徑對齊。
v0.2.0: Phase 9 完成 — ETTerms.SerialMcp(stdio MCP server)+ GUI SerialBridgeServer(named pipe \\.\pipe\etterms-serial)上線,提供 serial_list / serial_attach / serial_write / serial_read / serial_detach 五個工具,AI 的 TX 在 GUI 以 [AI] 標色即時 echo;視窗 / 工作列 / About 改用 Choco 圖示,標題列顯示版本號。見 docs/serial-mcp-guide.md。
v0.1.2: 新增 sprintf2(TeraTerm 相容 C printf 格式化);wait 改為命中關鍵字後須等裝置安靜(SettleMs 預設 300ms)才接受並取「最後一次」出現,排除輸出中途的指令回顯(避免腳本搶跑)。
技術棧
- UI: C# .NET 8 WinForms(
net8.0-windows,UseWindowsForms,Nullable=enable) - SSH: SSH.NET(
Renci.SshNet)— Shell + SFTP - Serial:
System.IO.Ports - Local Shell: Windows ConPTY(
CreatePseudoConsole)— PowerShell / Bash / Cmd - 終端機渲染: 自繪 VT100 / ANSI 控制項(owner-drawn)
- 腳本:
TTLInterpreter(從For_AI/MyTeraTerm移植,改驅動ISessionChannel) - 連線儲存: SQLite(
Microsoft.Data.Sqlite) - 密碼儲存: Windows Credential Manager(不落地明碼)
- PDU: SnmpSharpNet(iPoMan II/III via SNMP)
- AI / MCP(選用): 兩個 stdio MCP server(官方 C# SDK
ModelContextProtocol)。ETTerms.SerialMcp:不自己開 COM port,經本機 named pipe 接上 GUI 持有的 serial session,AI 的 TX/RX 同步顯示在 GUI。ETTerms.PduMcp(v0.3.0):直接打 SNMP 控制 PDU 插座,非獨佔故不需 GUI 在跑。皆暴露給 Kiro CLI / Claude CLI - 設定持久化: JSON →
%LocalAppData%\ETTerms\settings.json
常用指令
# 建置 / 執行
dotnet build
dotnet run --project src\ETTerms\ETTerms.csproj
# 加套件
dotnet add src\ETTerms package SSH.NET
# 打包(見「Publish / 打包慣例」)—— 兩種版本都產出,輸出到 src\ETTerms\Publish\
# GUI publish 會「自動」把 ETTerms.SerialMcp 與 ETTerms.PduMcp 一併發到各自的子資料夾
# (ETTerms.csproj 的 PublishMcpServers target,AfterTargets=Publish),且 MCP 跟隨 GUI 的 self-contained 設定。
$ver = ([regex]::Match((Get-Content src\ETTerms\ETTerms.csproj -Raw), '<Version>([^<]+)</Version>')).Groups[1].Value
# A. 框架相依版(需目標機已裝 .NET 8 Desktop Runtime)→ ETTerms_v{Version}\
$root = "src\ETTerms\Publish\ETTerms_v$ver"
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 免安裝版(runtime 內含,免裝、免管理員)→ ETTerms_v{Version}_portable\
$proot = "src\ETTerms\Publish\ETTerms_v${ver}_portable"
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"
# 註冊 Serial MCP server(給 AI agent 操作 serial)
# 推薦:GUI Settings → AI MCP 分頁,對 Claude Code / Kiro 按 Setup 一鍵註冊(McpRegistrar)。
# 或手動 CLI:
kiro-cli mcp add --name serial --command dotnet --args "run --project src\ETTerms.SerialMcp\ETTerms.SerialMcp.csproj"
開發慣例
- 命名: PascalCase 類別 / 方法,
_camelCase私有欄位;檔名 = 類別名。 - Publish / 打包: 輸出到
src\ETTerms\Publish\;主 exe 改名為ETTerms v{Version}.exe;ETTerms.SerialMcp與ETTerms.PduMcp一併發到其下ETTerms.SerialMcp\、ETTerms.PduMcp\子資料夾(且跟隨 GUI 的 self-contained 設定)。兩種版本都產出:框架相依ETTerms_v{Version}\(--self-contained false,需裝 .NET 8 Desktop Runtime)+ portable 免安裝ETTerms_v{Version}_portable\(--self-contained true,runtime 內含)。不要開 trimming(WinForms 反射)。詳見 ARCHITECTURE.md。 - 分層: UI(
App/)只認ISessionChannel抽象,不直接相依 SSH.NET / SerialPort。 - 執行緒: channel I/O 在背景;所有 UI 更新一律
Control.Invoke回 UI thread。 - commit: 走 Conventional Commits(
feat:/fix:/refactor:…)。 - 參考專案不改:
For_AI/KKTerm-main、For_AI/MyTeraTerm只讀對照,不在 repo 內修改。
注意事項 / 禁止事項
- 🚫 密碼絕不寫進 SQLite / 程式碼 / log,一律走 Windows Credential Manager。
- 🚫 不嵌 TeraTerm、不依賴 com0com —— ETTerms 走全原生(這是與舊版 MyTeraTerm 的關鍵差異)。
- 🚫 不要把
For_AI/內容 commit 進 git。 - ⚠️ Serial COM port 同時只能被一個 session 開啟,開啟前檢查可用性。
- ⚠️ COM port 由 GUI 唯一持有;Serial MCP server(
ETTerms.SerialMcp)不自己開 port,而是經本機 named pipe 接上 GUI 已開啟的 serial session 來收發。AI 操作前該 port 必須已在 GUI 開啟(pipe 找不到對應 session 就回錯誤)。 - ⚠️ VT 相容性以常見情境(VT100 / 常見 ANSI)為主,冷門 escape 後補,不阻塞 GUI 進度。
- ⚠️ 本專案無伺服端祕密 / 無 DB 密碼 / 無 EC2 / 無 VM,因此不套用 AWS / VirtualBox 部署流程。
- ⚠️ Group 同步指令(
waitall/sendlnall/sendlngroup)只能在 Run Group 模式使用;▶ Script和▶ Run All須拒絕含這些指令的腳本。 - ⚠️ Group
waitall的 barrier 行為:一個成員失敗/停止,其他成員會停在 barrier 等 —— 這是刻意設計(整組一起停下來),不是 bug,勿改成 RemoveParticipant。 - ⚠️
SshChannel.Resize用反射挖 SSH.NET 私有_channel呼叫SendWindowChangeRequest(該 API 未公開)。升級 SSH.NET 版本時必須驗證 resize 仍有效(連上後拉視窗大小看遠端 TUI 是否跟著變)。
資料夾用途
src/ETTerms/— 主應用程式(WinForms 視窗外殼 + 連線 / 終端機 / 腳本引擎)。src/ETTerms.SerialMcp/— ✅ stdio MCP server(給 AI agent 收發 serial)。獨立行程,但不直接開 COM port:經本機 named pipe 連到 GUI 的SerialBridgeServer,由 GUI 代為讀寫實體 port;net8.0 console +ModelContextProtocolSDK。src/ETTerms.PduMcp/— ✅ stdio MCP server(給 AI agent 控制 SNMP PDU,v0.3.0)。獨立行程,直接打 SNMP,不經 GUI、GUI 不開著也能用;net8.0 console +ModelContextProtocol,PDU 邏輯用ETTerms.PduCore。src/ETTerms.PduCore/— ✅ PDU SNMP 控制共用庫(v0.4.0)。GUI 與 PduMcp 共用的唯一PduController(先前兩份複製已移除);診斷 log 走建構子注入委派(GUI→AppLogger、MCP→stderr);含批次查詢GetAllPortsStatus()。For_AI/— AI 協作素材與參考專案(KKTerm-mainUI 參考、MyTeraTermScript 參考)。整個資料夾 gitignored,僅供開發對照。- 本專案無
secret/資料夾:沒有伺服端祕密 / DB 密碼 / compile-time secret,連線密碼一律走 Windows Credential Manager。