Remote monitoring bar:
- Slim 24px bar at bottom of every SSH terminal
- CPU, RAM, disk, network stats polled every 5s via exec channel
- Cross-platform: Linux (/proc), macOS (vm_stat/sysctl), FreeBSD
- Color-coded thresholds: green/amber/red
- No agent installation — standard POSIX commands only
SFTP follows active tab:
- Added :key="activeSessionId" to FileTree component
- Vue recreates FileTree when session changes, reinitializing SFTP
CWD tracking fix (macOS + all platforms):
- Old approach: exec channel pwd — returns HOME, not actual CWD
- New approach: passive OSC 7 parsing in the output stream
- Scans for \e]7;file://host/path\a without modifying data
- Works with bash, zsh, fish on both Linux and macOS
- Zero corruption risk — data passes through unmodified
- Includes URL percent-decoding for paths with spaces
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resizable panel:
- Drag handle on left border, pointer events, 320px–1200px range
SFTP MCP tools:
- sftp_list, sftp_read, sftp_write — full HTTP endpoints + bridge tools
- SftpService now Clone for MCP server sharing
Active session context:
- mcp_get_session_context — last 20 lines of any session's scrollback
Error watcher:
- Background scanner every 2s across all sessions
- 20+ patterns: permission denied, OOM, segfault, disk full, etc.
- mcp:error events emitted to frontend
- Sessions auto-registered on SSH connect
Configurable launch presets:
- Settings → AI Copilot section with preset editor
- Name + command pairs, stored in settings table as JSON
- One-click preset buttons in copilot panel empty state
- Defaults: Claude Code, Gemini CLI, Codex CLI
- User can set custom commands (e.g. claude --dangerously-skip-permissions)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resizable panel:
- Drag handle on left border of copilot panel
- Pointer events for smooth resize (320px–1200px range)
SFTP MCP tools:
- sftp_list: list remote directories
- sftp_read: read remote files
- sftp_write: write remote files
- Full HTTP endpoints + bridge tool definitions
Active session context:
- mcp_get_session_context command returns last 20 lines of scrollback
- Frontend can call on tab switch to keep AI informed
Error watcher:
- Background scanner runs every 2 seconds across all sessions
- 20+ error patterns (permission denied, OOM, segfault, disk full, etc.)
- Emits mcp:error events to frontend with session ID and matched line
- Sessions auto-registered with watcher on connect
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auto-inject CLAUDE_MCP_SERVERS env var when copilot PTY spawns,
so Claude Code auto-discovers wraith-mcp-bridge without manual config
- RDP screenshot_png_base64() encodes frame buffer as PNG via png crate
- Bridge binary exposes terminal_screenshot tool returning MCP image
content (base64 PNG with mimeType) for multimodal AI analysis
- MCP session list now includes RDP sessions with dimensions
- /mcp/screenshot HTTP endpoint on the internal server
"Screenshot that RDP session, what's the error?" now works.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete MCP communication pipeline:
Backend HTTP server (axum on localhost:0):
- POST /mcp/sessions — list active sessions
- POST /mcp/terminal/read — read scrollback (ANSI stripped)
- POST /mcp/terminal/execute — send command + marker, capture output
- Port written to data_dir/mcp-port at startup
- Shares SshService and ScrollbackRegistry with AppState via Clone
Bridge binary (wraith-mcp-bridge):
- Speaks JSON-RPC 2.0 over stdio (MCP protocol)
- Translates tool calls to HTTP requests against running Wraith
- Implements initialize, tools/list, tools/call
- Exposes: terminal_read, terminal_execute, list_sessions
- Reads MCP port from data_dir/mcp-port
Auto-config:
- PTY spawn injects WRAITH_MCP_BRIDGE env var
- SshService and ScrollbackRegistry derive Clone for sharing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Infrastructure for the Wraith Terminal MCP server:
- ScrollbackBuffer: 64KB circular buffer per session with ANSI stripping
- ScrollbackRegistry: DashMap registry shared between output loops and MCP
- SSH output loop feeds scrollback in addition to emitting events
- PTY output loop feeds scrollback in addition to emitting events
- mcp_terminal_read: read last N lines from any session (ANSI stripped)
- mcp_terminal_execute: send command + marker, capture output until marker
- mcp_list_sessions: enumerate all active SSH sessions with metadata
8 new scrollback tests (ring buffer, ANSI strip, line limiting).
95 total tests, zero warnings.
Bridge binary and auto-config injection to follow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tauri's built-in dragDropEnabled intercepts all drag events in the
WebView for native file drop handling. This prevents HTML5 drag-and-drop
between elements within the page. Disabled so tab reordering works.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Buttons resist drag in WebView2. Switched to div[role=tab] with
select-none. Added setData() call on dragstart — required by most
WebView implementations to actually initiate the drag operation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Status indicators:
- Session.status field tracks connected/disconnected state
- Listens for ssh:close and ssh:exit backend events
- Tab dot turns red when connection drops (green=SSH, blue=RDP, red=dead)
Draggable tabs:
- HTML5 drag-and-drop on tab buttons
- Blue left-border indicator shows drop target
- moveSession() in store reorders the sessions array
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tokio::task::spawn_blocking panics when called without a tokio runtime
context. Sync Tauri command handlers may not have one. The PTY reader
loop is long-lived anyway — std:🧵:spawn is more correct for
persistent blocking I/O and doesn't require a runtime guard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ripped out the Gemini API stub (ai/mod.rs, ai_commands.rs, GeminiPanel.vue)
and replaced it with a local PTY terminal in the sidebar panel. Users select
a shell (bash/zsh/sh on Unix, PowerShell/CMD/Git Bash on Windows), launch it,
and run claude/gemini/codex or any CLI tool directly.
Backend:
- New PtyService module using portable-pty (cross-platform PTY)
- DashMap session registry (same pattern as SshService)
- spawn_blocking output loop (portable-pty reader is synchronous)
- 5 Tauri commands: list_available_shells, spawn_local_shell, pty_write,
pty_resize, disconnect_pty
Frontend:
- Parameterized useTerminal composable: backend='ssh'|'pty'
- convertEol=false for PTY (PTY driver handles LF→CRLF)
- CopilotPanel.vue with shell selector, launch/kill, session ended prompt
- Ctrl+Shift+G toggle preserved
Tests: 87 total (5 new PTY tests), zero warnings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced the openssl CLI fallback with pure Rust crypto for EC private
keys in SEC1 format (-----BEGIN EC PRIVATE KEY-----). Handles PKCS#5
encrypted keys (AES-128-CBC + MD5 EVP_BytesToKey KDF) and converts to
PKCS#8 PEM that russh can parse natively.
All crypto crates (md5, aes, cbc, sec1, pkcs8) were already in the dep
tree via russh — just promoted to direct dependencies. Zero new binary
dependencies, works on Windows without openssl installed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tauri's NSIS bundler creates a desktop shortcut unconditionally. Added
a POSTINSTALL hook that deletes it immediately after creation. Start
menu shortcut remains. Users who want a desktop shortcut can create
one manually.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a file picker next to the Private Key textarea in the credential
dialog. Users can browse for key files (.pem, .key, id_rsa, id_ed25519,
etc.) instead of copy-pasting PEM content. Uses standard FileReader API
— no additional Tauri plugins needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
russh only parses 4 PEM headers: OPENSSH, RSA, PKCS8, ENCRYPTED PKCS8.
EC keys (-----BEGIN EC PRIVATE KEY-----) with PKCS5 encryption silently
failed with "Could not read key".
Fix adds two fallbacks:
1. If russh can't parse the key, convert to PKCS8 via `openssl pkey`
which handles EC, DSA, and all other OpenSSL-supported formats
2. If the input doesn't start with -----BEGIN, try reading it as a
file path (supports ~ expansion) for keys stored on disk
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: The output reader loop held Arc<TokioMutex<Channel>> while
calling ch.wait().await. After the initial prompt rendered and the server
went idle, wait() blocked indefinitely holding the lock. ssh_write()
could never acquire the mutex to send keystrokes. Permanent deadlock.
Fix: Separated read/write paths. The output loop now owns the Channel
exclusively via tokio::select!, receiving resize/shutdown commands through
an mpsc channel. Writes go through Handle::data(channel_id, data) which
bypasses the Channel entirely — no shared mutex, no deadlock.
Also killed all compiler warnings (unused imports in rdp module).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Clipboard paste (rdp_send_clipboard): simulates typing each character
via scancode key press/release events. Full ASCII coverage including
all symbols, numbers, and shifted characters. Handles 32-char
generated passwords without manual typing.
2. Keyboard grab defaults to ON so RDP sessions accept keyboard input
immediately without requiring the user to click the toolbar toggle.
3. Frame dirty flag: GraphicsUpdate sets an AtomicBool, get_frame only
encodes + returns base64 when dirty (returns empty string otherwise).
Eliminates ~8MB/frame base64 encoding on unchanged frames at 30fps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The watch on isActive never fired on initial mount because the value
starts as true (Vue watch only triggers on changes). Added explicit
focus + fit in onMounted with a short delay for DOM readiness.
Also added @click handler on container as fallback focus mechanism.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The app had no capabilities file, so Tauri v2's ACL blocked all
frontend listen() calls. SSH connections succeeded on the Rust side
but the terminal never received data events, appearing as "nothing
happened." Grants core:default, core:event:default, core🪟default,
and shell:allow-open.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ConnectionRecord.tags changed from String to Vec<String> so the
frontend receives a proper array instead of a raw JSON string.
The old behavior caused v-for to iterate characters, corrupting
the connection display in the sidebar.
- DevTools now only auto-opens in debug builds (cfg(debug_assertions)),
not in production.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The workflow created a release and uploaded to the packages registry,
but never attached the .exe as a release asset — so the release page
had no downloads. Now uploads each NSIS installer to the release.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The template was cut off mid-attribute, missing the message list rendering,
chat input with @keyup.enter="handleSend", and send button. This caused
vue-tsc to flag handleSend as unused, breaking CI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend cleanup (Gemini):
- Strip verbose doc comments across SSH, RDP, and command modules
- Add 10s timeout on SSH connect/auth, 15s on RDP connection
- Fix macOS data directory to ~/Library/Application Support/Wraith
- Add generic disconnect_session command
- Simplify SFTP setup and error handling
- Inline AppState field construction
Gemini AI XO integration:
- Add GeminiService (src-tauri/src/ai/) with API Key, Service Account,
and Google Account (OAuth2) authentication methods
- Add ai_commands (set_gemini_auth, gemini_chat, is_gemini_authenticated)
- Add GeminiPanel.vue — collapsible chat sidebar with multi-auth UI
- Wire Ctrl+Shift+G toggle and status bar AI button in MainLayout
- Add reqwest + anyhow dependencies
Bugfix:
- Fix dropped modulo operator in Ctrl+Tab/Ctrl+Shift+Tab handlers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove redundant doc comments and section headers across SSH, RDP, and command modules
- Add 10s timeout on SSH connect/auth, 15s timeout on RDP connection
- Fix macOS data directory to use ~/Library/Application Support/Wraith
- Add generic disconnect_session command alongside disconnect_ssh
- Simplify SFTP setup and RDP error handling
- Add explicit label/url to main window config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Never manually sync version again. CI reads the tag, patches
the config before building. Also bumped to 1.1.5.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added devtools Cargo feature, auto-open DevTools on startup
so we can see frontend console errors on Windows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added decrypt_password and decrypt_ssh_key Tauri commands.
Connect flow now resolves credentialId → decrypted credentials
from the vault. Falls back to window.prompt on auth failure.
Fixed case-sensitive error string matching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed ImportDialog, MobaXTerm first-run prompt, import menu
item, and all related refs. 6 connections — entered by hand.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Writes to %APPDATA%\Wraith\wraith-startup.log since release
builds suppress all console output via windows_subsystem.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Re-enable Tauri NSIS bundler (embeds frontend in exe). Runner
runs as ActRunner service account with proper user profile,
so Tauri's downloaded NSIS/tools should work. Removed manual
NSIS step and template file from workflow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
act_runner v0.2.11 doesn't support step outputs. Extract version
from github.ref_name inline in every step. Use absolute paths
for NSIS. Write nsi file with System.IO to avoid encoding issues.
Store Azure token in temp file instead of step output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PowerShell was eating $INSTDIR/$PROGRAMFILES64 and $version.
Use single-quoted here-string with placeholder replacement.
Pass full path to makensis instead of Push-Location.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tauri's bundled makensis can't run under SYSTEM account.
Use --no-bundle, then build installer with system NSIS
directly — same pattern as the old Go pipeline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tauri's downloaded NSIS can't run under SYSTEM account.
Point to system-installed NSIS at Program Files (x86).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
act_runner v0.2.11 doesn't support GITHUB_PATH reliably.
Set env.EXTRA_PATH at job level, prepend in each step.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>