Phase 9 (AI integration) complete -- AI agents can send/receive serial while the user watches live in the GUI, with the GUI as sole COM-port owner. - ETTerms.SerialMcp: stdio MCP server (net8.0 + ModelContextProtocol SDK) exposing serial_list / serial_attach / serial_write / serial_read (waitFor + timeoutMs) / serial_detach; forwards over named pipe. - GUI SerialBridgeServer (pipe etterms-serial) + SerialBridge endpoint; SessionPage.WriteFromAi echoes MCP-sourced TX tagged [AI] (magenta). - App icon: window title bar / taskbar / exe now use Choco_256x256.ico (embedded via AppAssets); title bar shows ETTerms Version vX.Y.Z. - About page: large Choco icon (Zoom, no crop). - Docs: ARCHITECTURE.md + CLAUDE.md mark Phase 9 done; docs/serial-mcp-guide.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
427 lines
19 KiB
Markdown
427 lines
19 KiB
Markdown
# ETTerms
|
||
|
||
[](README.md) [](README.zh-TW.md)
|
||
|
||
> A native Windows terminal workspace (C# .NET 8 WinForms) — **SSH**, **Serial Port**, and **local Shell (ConPTY)** in one window, with a **TTL scripting engine** ported from MyTeraTerm for automation, plus an optional **Serial MCP server** that lets AI agents (Kiro CLI / Claude CLI) drive the serial port directly. Standalone, no cloud, no login.
|
||
|
||
       
|
||
|
||
---
|
||
|
||
## 📖 Table of Contents
|
||
|
||
- [✨ Features](#-features)
|
||
- [🖼️ Layout](#️-layout)
|
||
- [💻 System Requirements](#-system-requirements)
|
||
- [📥 Installation](#-installation)
|
||
- [🚀 Quick Start](#-quick-start)
|
||
- [📚 Usage Guide](#-usage-guide)
|
||
- [🤖 TTL Scripting](#-ttl-scripting)
|
||
- [👥 Group Sync Execution](#-group-sync-execution)
|
||
- [⚡ PDU Power Control](#-pdu-power-control)
|
||
- [🤖 AI / MCP Integration](#-ai--mcp-integration)
|
||
- [🔐 Data & Security](#-data--security)
|
||
- [🔧 Troubleshooting](#-troubleshooting)
|
||
- [🔨 Building from Source](#-building-from-source)
|
||
- [📁 Project Structure](#-project-structure)
|
||
- [🤝 Contributing](#-contributing)
|
||
- [📜 Version History](#-version-history)
|
||
- [📄 License](#-license)
|
||
- [🙏 Acknowledgments](#-acknowledgments)
|
||
|
||
---
|
||
|
||
## ✨ Features
|
||
|
||
### Core Functionality
|
||
|
||
- 🖥️ **Multiple protocols in one workspace**
|
||
- **SSH** (`SSH.NET`): password / private key / keyboard-interactive auth, with built-in SFTP
|
||
- **Serial Port** (`System.IO.Ports`): configurable COM port, baud, data bits, parity, stop bits, handshake
|
||
- **Local Shell** (Windows ConPTY): launch PowerShell / Cmd / Bash directly
|
||
|
||
- 🪟 **Tiling tabbed workspace**
|
||
- One-click grid layouts: `1×1 / 1×2 / 2×1 / 2×2 / 2×3 / 3×3`
|
||
- Per-pane manual split: `↔` horizontal / `↕` vertical, drag splitters to resize proportionally
|
||
- Each pane hosts its own mini tab strip for multiple connections
|
||
|
||
- 🗂️ **Editable connection sidebar (Saved Connections)**
|
||
- Nested folders with connection-count badges, expand / collapse all
|
||
- Live filter by name / host, matching branches auto-expand
|
||
- Context menu & toolbar: add / rename / delete, drag to re-categorize
|
||
- Double-click to open in the active pane; ad-hoc quick connect (not saved)
|
||
|
||
- 🤖 **TTL script automation**
|
||
- TTL (Tera Term Language) interpreter ported and extended from MyTeraTerm
|
||
- Runs `.ttl` against the active session, showing file / line / current command live
|
||
- Supports `send` / `sendln` / `wait` / `if` / `while` / variable math / file logging
|
||
- Long-running `wait` / `while` can be aborted anytime with **■ Stop**
|
||
|
||
- 👥 **Group sync execution**
|
||
- Assign tabs to Group 1/2/3 and run one script across the whole group at once
|
||
- Sync commands: `waitall` (barrier), `sendlnall` (send after all arrive), `sendlngroup` (target a member)
|
||
|
||
- ⚡ **PDU power control (optional)**
|
||
- Control PDU outlets over SNMP (`SnmpSharpNet`) to power-cycle devices during tests
|
||
- Script commands: `pduconnect` / `pductrl`
|
||
|
||
- 🤖 **AI / MCP integration (optional, planned)**
|
||
- The **GUI owns the COM port**; a standalone stdio **Serial MCP server** bridges to it over a local named pipe for **Kiro CLI / Claude CLI**
|
||
- Tools: `serial_list` / `serial_attach` / `serial_write` / `serial_read` / `serial_detach`
|
||
- AI's serial TX/RX shows live in the GUI tagged `[AI]` — open the port in the GUI first, then let the AI attach
|
||
|
||
- 📊 **Session RX logging**
|
||
- `logopen` / `logwrite` / `logclose` write session output to file
|
||
|
||
### Terminal Rendering
|
||
|
||
- 🎨 Owner-drawn VT100 / ANSI control, double-buffered cell grid
|
||
- 🌑 KKTerm-style dark theme (incl. DWM dark title bar)
|
||
- 🔤 Configurable font / size / palette / scrollback
|
||
- 📋 Select / copy / paste
|
||
|
||
---
|
||
|
||
## 🖼️ Layout
|
||
|
||
```
|
||
┌──────┬─────────────────────┬───────────────────────────────────────┐
|
||
│ ▣ T │ Saved Connections │ Workspace (tiling) │
|
||
│ ▣ S │ 🔍 search │ ┌─────────────────┬─────────────────┐ │
|
||
│ ▣ ⚙ │ ▾ 📁 Servers (2) │ │ [tab1][tab2] + │ [tab1] + │ │
|
||
│ │ ▸ SSH srv-01 │ │ │ │ │
|
||
│ icon │ ▸ SSH nas │ │ TerminalView │ TerminalView │ │
|
||
│ rail │ ▾ 📁 Boards (2) │ │ (owner-drawn) │ (owner-drawn) │ │
|
||
│ │ ▸ COM3 @115200 │ │ │ │ │
|
||
│ │ ▸ COM7 @9600 │ ├─────────────────┴─────────────────┤ │
|
||
│ │ │ │ [Group1-A] ▶ Script ■ Stop │ │
|
||
└──────┴─────────────────────┴───────────────────────────────────────┘
|
||
Activity Rail Sidebar Tabbed / Tiling Workspace
|
||
```
|
||
|
||
> The Activity Rail switches the three main views — **Terminal / Scripts / Settings**. The sidebar manages saved connections; the workspace holds multiple sessions across tabs and tiled panes.
|
||
|
||
---
|
||
|
||
## 💻 System Requirements
|
||
|
||
| Item | Requirement |
|
||
|------|-------------|
|
||
| OS | Windows 10 (1809+) / Windows 11 |
|
||
| Runtime | .NET 8 Desktop Runtime (`Microsoft.WindowsDesktop.App 8.0.x`) |
|
||
| Display | 1600×900 or higher recommended |
|
||
| Optional hardware | USB-to-Serial adapter (Serial), SNMP-capable PDU (power control) |
|
||
|
||
Check the runtime:
|
||
|
||
```powershell
|
||
dotnet --list-runtimes | findstr WindowsDesktop
|
||
```
|
||
|
||
---
|
||
|
||
## 📥 Installation
|
||
|
||
Source build for now (no binary release yet).
|
||
|
||
```powershell
|
||
git clone <repo-url> ETTerms
|
||
cd ETTerms
|
||
dotnet build
|
||
dotnet run --project src\ETTerms\ETTerms.csproj
|
||
```
|
||
|
||
Full steps in [Building from Source](#-building-from-source).
|
||
|
||
---
|
||
|
||
## 🚀 Quick Start
|
||
|
||
### Create an SSH connection
|
||
|
||
1. Launch ETTerms; on the left Activity Rail switch to **Terminal**
|
||
2. In the sidebar click **+ New Connection** and pick type **SSH**
|
||
3. Enter `Host` / `Port` (default 22) / `Username`, choose auth (password / private key)
|
||
4. Double-click the connection → opens a tab in the active pane
|
||
|
||
### Create a Serial connection
|
||
|
||
1. Sidebar **+ New Connection**, type **Serial**
|
||
2. Pick `COM port` and `BaudRate` (e.g. `COM3` / `115200`)
|
||
3. Double-click to open
|
||
|
||
### Run a TTL script
|
||
|
||
1. With a tab connected, click **▶ Script** on the script bar and pick a `.ttl`
|
||
2. The left status bar shows the running line and command live
|
||
3. Press **■ Stop** to abort if needed
|
||
|
||
---
|
||
|
||
## 📚 Usage Guide
|
||
|
||
### Activity Rail
|
||
|
||
Switches the three main views: **Terminal** (workspace), **Scripts** (edit & run), **Settings** (preferences).
|
||
|
||
### Connection Sidebar (Saved Connections)
|
||
|
||
| Action | How |
|
||
|--------|-----|
|
||
| Add folder / connection | Toolbar button or context menu |
|
||
| Rename / delete | Context menu |
|
||
| Categorize | Drag a connection or folder into a target folder (cannot drop into its own descendant) |
|
||
| Search | Top search box, live filter by name / host |
|
||
| Open connection | Double-click → opens in the active pane |
|
||
|
||
### Workspace Tiling
|
||
|
||
- One-click grid layouts: `1×1 / 1×2 / 2×1 / 2×2 / 2×3 / 3×3`
|
||
- Per-pane actions (top-right): `↔` split horizontally, `↕` split vertically, `+` new tab, `✕` close
|
||
- Closing a pane collapses the split so siblings fill the space; at least one pane remains
|
||
- The active pane is outlined with an accent border
|
||
|
||
### Terminal
|
||
|
||
- Incoming channel bytes → ANSI parser → screen buffer → painted
|
||
- Keyboard input is encoded to byte sequences sent to the remote
|
||
- Supports scrollback, select / copy / paste; font and palette under **Settings**
|
||
|
||
---
|
||
|
||
## 🤖 TTL Scripting
|
||
|
||
On any connection tab's script bar, click **▶ Script** to load a `.ttl` and run it against that connection. Full syntax and examples in
|
||
[docs/ttl-script-reference.md](docs/ttl-script-reference.md); sample scripts in `tools/scripts/`.
|
||
|
||
### Supported Commands
|
||
|
||
| Command | Description |
|
||
|---------|-------------|
|
||
| `send 'text'` | Send text (no newline) |
|
||
| `sendln 'text'` | Send text plus `\r\n` |
|
||
| `wait 'string'` | Wait until the string appears in the RX buffer (infinite by default, cancellable via Stop); sets `result=1` on match |
|
||
| `flushrecv` | Clear the RX buffer |
|
||
| `pause seconds` | Pause N seconds (cancellable via Stop) |
|
||
| `timeout = seconds` | `wait` timeout; `0` = infinite, `N>0` aborts the script with an error on timeout |
|
||
| `if … then` / `elseif … then` / `else` / `endif` | Conditional branching (nestable) |
|
||
| `while …` / `endwhile` | Loop (nestable, cancellable via Stop) |
|
||
| `name = value` | Variable assignment; integer `+ - * /` and strings; built-in `result` |
|
||
| `logopen 'file'` | Open a log file (overwrite) |
|
||
| `logwrite 'text'` | Write one line to the log |
|
||
| `logclose` | Close the log (auto-closed at script end) |
|
||
| `messagebox 'msg'` | Show a topmost dialog |
|
||
| `; comment` | Inline comment (from `;` to end of line) |
|
||
| `:label` | Label line (skipped) |
|
||
|
||
**Comparison operators** (`if` / `elseif` / `while`): `>=` `<=` `>` `<` `==` `!=` `=`, or none (non-zero = true).
|
||
|
||
### Example: auto login
|
||
|
||
```ttl
|
||
; tools/scripts/login.ttl
|
||
timeout = 10
|
||
wait 'login: '
|
||
sendln 'root'
|
||
wait 'Password: '
|
||
sendln 'toor'
|
||
wait '# '
|
||
sendln 'uname -a'
|
||
```
|
||
|
||
---
|
||
|
||
## 👥 Group Sync Execution
|
||
|
||
Assign multiple tabs to one Group and run a single script across all of them — ideal for syncing multiple DUTs.
|
||
|
||
1. **Right-click a tab** → set as Group 1 / 2 / 3 (or clear); the cell footer shows a `[Group1-A]` label
|
||
2. Click **▶ Group1 / ▶ Group2 / ▶ Group3** on the toolbar to run across the whole group
|
||
|
||
| Command | Description |
|
||
|---------|-------------|
|
||
| `waitall 'string'` | All members wait for the keyword, then continue together (`System.Threading.Barrier`) |
|
||
| `sendlnall 'text'` | Each member sends once all have arrived |
|
||
| `sendlngroup N 'text'` | Only the specified member sends |
|
||
|
||
> ⚠️ Group sync commands work **only in Run Group mode**; `▶ Script` and `▶ Run All` reject scripts containing them and show a warning.
|
||
|
||
---
|
||
|
||
## ⚡ PDU Power Control
|
||
|
||
Control PDU outlets over SNMP to power-cycle a DUT from within a script.
|
||
|
||
```ttl
|
||
; connect PDU (device 2 = iPoMan II 1202, IP 192.168.1.21)
|
||
pduconnect 2 192.168.1.21
|
||
|
||
pductrl 2 1 0 ; turn port 1 off
|
||
pause 5
|
||
pductrl 2 1 1 ; turn port 1 on
|
||
wait 'login: '
|
||
```
|
||
|
||
| Command | Syntax | Description |
|
||
|---------|--------|-------------|
|
||
| `pduconnect` | `pduconnect <device> <ip>` | Connect to a PDU |
|
||
| `pductrl` | `pductrl <device> <port> <0\|1>` | Set a port off(0) / on(1) |
|
||
|
||
---
|
||
|
||
## 🤖 AI / MCP Integration
|
||
|
||
> 🔜 **Planned (Phase 9).** Let AI agents (**Kiro CLI / Claude CLI**) send/receive on a serial port **while you watch it live in the ETTerms GUI**.
|
||
|
||
**Key design: the GUI owns the COM port; the MCP server never opens it.** A COM port can be opened by only one process at a time, so rather than the MCP server grabbing it, the GUI is the sole owner and runs a local **named pipe server** (`SerialBridgeServer`). The standalone `ETTerms.SerialMcp` (launched by Kiro/Claude CLI) is a thin client: every `write` / `read` is forwarded over the pipe to the GUI, which does the real port I/O. AI's TX is echoed into the terminal tagged `[AI]`, so RX/TX flow through the GUI channel and what you see is exactly what the AI sees.
|
||
|
||
Full closed loop — all inside the ETTerms GUI:
|
||
- **Tab 1:** a Serial session holding e.g. COM3 (shows traffic live; AI's writes tagged `[AI]`)
|
||
- **Tab 2:** a PowerShell (ConPTY) tab running `kiro-cli`, which spawns `ETTerms.SerialMcp`, which connects back to the pipe
|
||
|
||
| Tool | Params | Description |
|
||
|------|--------|-------------|
|
||
| `serial_list` | — | List serial sessions currently **open in the GUI** (name + baud) |
|
||
| `serial_attach` | portName | Bind to a GUI serial session by COM name (required before write/read) |
|
||
| `serial_write` | text, appendNewline? | Send text via the GUI (shown live tagged `[AI]`) |
|
||
| `serial_read` | waitFor?, timeoutMs? | Drain accumulated RX; optionally wait for a substring / timeout |
|
||
| `serial_detach` | — | Unbind (does **not** close the GUI's port) |
|
||
|
||
Register in Kiro CLI:
|
||
|
||
```powershell
|
||
kiro-cli mcp add --name serial --command dotnet --args "run --project src\ETTerms.SerialMcp\ETTerms.SerialMcp.csproj"
|
||
```
|
||
|
||
Or add it to `agent.json` under `mcpServers` (Claude CLI uses an equivalent `mcpServers` config). **Open the target serial connection in the ETTerms GUI first**, then tell the AI: *"list serial sessions, attach to COM3, send `AT` and read the reply."*
|
||
|
||
> ⚠️ The COM port is owned solely by the GUI. The MCP server attaches over the pipe — it never opens the port itself, so there's no GUI-vs-server conflict.
|
||
|
||
📖 Step-by-step setup: [docs/serial-mcp-guide.md](docs/serial-mcp-guide.md).
|
||
|
||
---
|
||
|
||
## 🔐 Data & Security
|
||
|
||
- **Standalone, no cloud:** connection metadata is stored in a local SQLite file (`%LocalAppData%\ETTerms\ettermsdb.sqlite`); no telemetry is sent.
|
||
- **Passwords never stored in plaintext:** SQLite holds only a `CredentialKey` pointing to **Windows Credential Manager**; actual passwords / key passphrases are accessed via `CredentialVault`.
|
||
- **SSH host keys:** the fingerprint is shown for confirmation on first connect (trust-on-first-use), then compared; mismatches warn.
|
||
- **Logs contain no secrets:** `AppLogger` and `logopen` record only host / port, never credentials.
|
||
- **No `secret/` directory:** a single-user desktop app with no server-side secrets, DB passwords, or compile-time secrets.
|
||
|
||
---
|
||
|
||
## 🔧 Troubleshooting
|
||
|
||
| Issue | Likely cause | Fix |
|
||
|-------|--------------|-----|
|
||
| Startup reports missing runtime | .NET 8 Desktop Runtime not installed | Install `Microsoft.WindowsDesktop.App 8.0.x`; verify with `dotnet --list-runtimes` |
|
||
| Serial connect fails / port busy | COM port held by another app | Close other terminal apps; a COM port can be opened by only one session at a time |
|
||
| SSH auth fails | Wrong credentials / key or host-key mismatch | Check auth settings; verify the host-key fingerprint |
|
||
| Script stuck on `wait` | Expected string never received | Check connection & baud, verify the `wait` string; set `timeout` or press ■ Stop |
|
||
| Group script rejected | Ran a script with Group commands via `▶ Script` | Use **▶ GroupN** instead |
|
||
|
||
More runbooks in [docs/runbooks/](docs/runbooks/).
|
||
|
||
---
|
||
|
||
## 🔨 Building from Source
|
||
|
||
### Prerequisites
|
||
|
||
- .NET 8 SDK (or SDK 9/10 + .NET 8 Desktop Runtime)
|
||
- Visual Studio 2022 or VS Code + C# extension
|
||
- Git
|
||
|
||
### Build & Run
|
||
|
||
```powershell
|
||
dotnet --list-sdks
|
||
dotnet build
|
||
dotnet run --project src\ETTerms\ETTerms.csproj
|
||
```
|
||
|
||
### Key NuGet Packages
|
||
|
||
| Package | Purpose |
|
||
|---------|---------|
|
||
| `SSH.NET` | SSH shell + SFTP |
|
||
| `System.IO.Ports` | Serial connections |
|
||
| `Microsoft.Data.Sqlite` | Connection metadata storage |
|
||
| `SnmpSharpNet` | PDU control (optional) |
|
||
|
||
### Publish (self-contained=false)
|
||
|
||
```powershell
|
||
dotnet publish src\ETTerms\ETTerms.csproj -c Release -r win-x64 --self-contained false
|
||
```
|
||
|
||
---
|
||
|
||
## 📁 Project Structure
|
||
|
||
```
|
||
ETTerms/
|
||
├── README.md # English (default)
|
||
├── README.zh-TW.md # Traditional Chinese
|
||
├── ARCHITECTURE.md # Full architecture doc
|
||
├── CLAUDE.md # Project memory & dev commands
|
||
├── ETTerms.slnx
|
||
├── docs/
|
||
│ ├── ttl-script-reference.md # TTL command reference
|
||
│ └── runbooks/ # Runbooks / troubleshooting
|
||
├── tools/scripts/ # Sample .ttl scripts
|
||
├── src/ETTerms/ # Main application (WinForms)
|
||
│ ├── App/ # Window shell: MainForm / ActivityRail / ConnectionSidebar / Workspace
|
||
│ ├── Terminal/ # Owner-drawn VT100: TerminalView / AnsiParser / ScreenBuffer / TerminalInput
|
||
│ ├── Sessions/ # Connection abstraction: ISessionChannel / SshChannel / SerialChannel / ShellChannel
|
||
│ ├── Connections/ # Connection data: Connection / ConnectionStore(SQLite) / CredentialVault
|
||
│ ├── Scripting/ # TTL engine: TTLInterpreter / ScriptRunner / GroupSyncContext / Pdu
|
||
│ └── Infrastructure/ # AppLogger / AppSettings / NativeTheme
|
||
└── src/ETTerms.SerialMcp/ # 🔜 Serial MCP server (stdio) — lets AI agents drive the serial port
|
||
```
|
||
|
||
**Core design:** every connection implements `ISessionChannel` (`Write(byte[])` + `event DataReceived`). `TerminalView` and `TTLInterpreter` only know this abstraction, so SSH / Serial / Shell look identical to upper layers — the key to "one script engine driving multiple connection types." See [ARCHITECTURE.md](ARCHITECTURE.md).
|
||
|
||
---
|
||
|
||
## 🤝 Contributing
|
||
|
||
1. Fork and create a feature branch: `git checkout -b feature/your-feature`
|
||
2. Follow [Conventional Commits](https://www.conventionalcommits.org/): `feat(serial): add auto-reconnect`
|
||
3. Push and open a Pull Request
|
||
|
||
**Conventions:** PascalCase types / methods, `_camelCase` private fields, filename = class name; the UI layer depends only on the `ISessionChannel` abstraction; channel I/O runs in the background and all UI updates go back to the UI thread via `Control.Invoke`.
|
||
|
||
---
|
||
|
||
## 📜 Version History
|
||
|
||
### v0.1.0 (in development)
|
||
|
||
- Phases 1–8 complete: window shell, connection sidebar, tiling workspace, Serial / SSH / local Shell (ConPTY) / SFTP
|
||
- Owner-drawn VT100 terminal rendering
|
||
- TTL scripting engine (ported from MyTeraTerm) + Group sync execution
|
||
- PDU power control (SNMP)
|
||
- Settings / About
|
||
- Packaging TBD
|
||
|
||
**Planned**
|
||
|
||
- Phase 9: Serial MCP server (`ETTerms.SerialMcp`) — let AI agents (Kiro CLI / Claude CLI) drive the serial port directly
|
||
|
||
---
|
||
|
||
## 📄 License
|
||
|
||
Licensed under the **MIT License** — see [LICENSE](LICENSE).
|
||
|
||
---
|
||
|
||
## 🙏 Acknowledgments
|
||
|
||
- **[KKTerm](https://github.com/)** — UI design reference (Activity Rail + tabbed workspace + Saved Connections)
|
||
- **MyTeraTerm** — source of the TTL scripting engine and `AppLogger`
|
||
- **[SSH.NET](https://github.com/sshnet/SSH.NET)**, **[SnmpSharpNet](http://www.snmpsharpnet.com/)** — open-source connectivity / SNMP libraries
|
||
- **Microsoft** — .NET 8, WinForms, ConPTY, Credential Manager
|