Commit Graph

61 Commits

Author SHA1 Message Date
Vantz Stockwell
a2770d3edf perf: double-buffered RDP frames + frontend rAF throttling
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 4m20s
Root cause: RDP was unresponsive due to frame pipeline bottleneck.
- get_frame() held tokio::Mutex while cloning 8.3MB, blocking the
  RDP session thread from writing new frames (mutex contention)
- Frontend fetched on every backend event with no coalescing
- Every GraphicsUpdate emitted an IPC event, flooding the frontend

Fix:
- Double-buffer: back_buffer (tokio::Mutex, write path) and
  front_buffer (std::sync::RwLock, read path) — reads never block writes
- get_frame() now synchronous, reads from front_buffer via RwLock
- Backend throttles frame events to every other GraphicsUpdate
- Frontend coalesces events via requestAnimationFrame
- RdpView props now reactive (computed) for correct resize behavior
- rdp_get_frame command no longer async (no .await needed)
- screenshot_png_base64 no longer async

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 09:19:40 -04:00
Vantz Stockwell
d4bfb3d5fd refactor: migrate UnlockLayout to Tailwind + extract ToolShell wrapper
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m54s
- Convert all ~40 inline styles in UnlockLayout.vue to Tailwind CSS v4 arbitrary-value classes, matching MainLayout.vue color conventions (CSS variables + hex arbitraries). Visual appearance preserved exactly.
- Create ToolShell.vue reusable wrapper that owns output/running state and execute/setOutput API via defineExpose.
- Refactor PingTool, TracerouteTool, DnsLookup, WhoisTool, BandwidthTest to use ToolShell — each tool now contains only its unique inputs and invoke calls. Zero vue-tsc errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 16:53:57 -04:00
Vantz Stockwell
b86e2d68d8 refactor: extract keyboard shortcuts composable + 5 UX bug fixes
- Extract handleKeydown into useKeyboardShortcuts.ts composable; reduces
  MainLayout by ~20 lines and isolates keyboard logic cleanly
- ConnectionTree: watch groups for additions and auto-expand new entries
- MonitorBar: generation counter prevents stale event listeners on rapid
  tab switching
- NetworkScanner: revoke blob URL after CSV export click (memory leak)
- TransferProgress: implement the auto-expand/collapse watcher that was
  only commented but never wired up
- FileTree: block binary/large file uploads with clear user error rather
  than silently corrupting — backend sftp_write_file is text-only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 16:53:46 -04:00
Vantz Stockwell
28619bba3f refactor: Vue 3 state, TypeScript, and lifecycle improvements
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m49s
- connectionsByGroup memoized as computed map — eliminates redundant filter on every render
- DockerPanel: replace any[] with typed DockerContainer/Image/Volume interfaces
- useRdp: replace ReturnType<typeof ref<boolean>> with Ref<boolean>
- SettingsModal: debounce sidebarWidth slider watch (300ms) to prevent rapid IPC calls
- useSftp: export cleanupSession() to prevent sessionPaths memory leak
- StatusBar, CommandPalette: migrate defineEmits to Vue 3.3+ tuple syntax
- SidebarToggle: replace manual v-model (defineProps + defineEmits) with defineModel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 16:53:17 -04:00
Vantz Stockwell
15c95841be merge: PERF-1/2/3 scrollback, RDP binary IPC, settings dedup 2026-03-29 16:41:14 -04:00
Vantz Stockwell
625a4500bc merge: MEM-1/2/3 event listener leak cleanup (resolved session.store.ts conflict) 2026-03-29 16:41:08 -04:00
Vantz Stockwell
fca6ed023e perf: PERF-1/2/3 scrollback bulk write, RDP binary IPC, settings dedup
- Scrollback: bulk copy_from_slice replaces byte-by-byte loop
- RDP frames: tauri::ipc::Response for zero-overhead binary transfer
- SettingsService: derive Clone, eliminate duplicate instance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:40:08 -04:00
Vantz Stockwell
24e8b1e359 fix: MEM-1/2/3 Tauri event listener leak cleanup
- TabBar: move listen() into onMounted, store UnlistenFn
- Session store: track per-session unlisten fns, clean on close
- useRdp: store frame unlisten in composable scope, call in stopFrameLoop

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:40:04 -04:00
Vantz Stockwell
ff9fc798c3 fix: resolve 8 Vue 3 lifecycle and component issues
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 4m5s
VUE-1: store workspace save interval ID, clear in onUnmounted
VUE-2: extract beforeunload handler to named function, remove in onUnmounted
VUE-3: move useTerminal() to <script setup> top level in DetachedSession
VUE-4: call useTerminal() before nextTick await in CopilotPanel launch()
VUE-5: remove duplicate ResizeObserver from LocalTerminalView (useTerminal already creates one)
VUE-6: store terminal.onResize() IDisposable, dispose in onBeforeUnmount
VUE-7: extract connectSsh(), connectRdp(), resolveCredentials() from 220-line connect()
VUE-8: check session protocol before ssh_resize vs pty_resize in TerminalView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 16:38:00 -04:00
Vantz Stockwell
2307fbe65f fix: terminal resize on tab switch + flickering from activity marking
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 4m8s
Resize fix:
- ResizeObserver now checks contentRect dimensions before fitting
- Ignores resize events when container width/height < 50px (hidden tab)
- Prevents xterm.js from calculating 8-char columns on zero-width containers

Flickering fix:
- markActivity throttled to once per second per session
- Was firing on every single data event (hundreds per second during
  active output), triggering Vue reactivity updates on the sessions
  array, causing tab bar and session container re-renders

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:48:43 -04:00
Vantz Stockwell
661490e925 perf: RDP event-driven frames + MCP terminal \r fix
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 4m28s
RDP performance overhaul:
- Switched from polling (rAF loop calling rdp_get_frame every tick)
  to event-driven rendering (backend emits rdp:frame:{id} when
  frame buffer updates, frontend fetches on demand)
- Eliminates thousands of empty IPC round-trips per second when
  the screen is idle
- Backend passes AppHandle into run_active_session for event emission
- Frontend uses listen() instead of requestAnimationFrame polling

MCP terminal fix:
- terminal_type and terminal_execute now send \r (carriage return)
  instead of \n (newline) — PTY terminals expect CR to submit
- Fixes commands not auto-sending, requiring manual Enter press

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:44:53 -04:00
Vantz Stockwell
d78cafba93 feat: terminal_type MCP tool + tab resize fix + close confirmation
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m54s
terminal_type MCP tool (19 tools total):
- Fire-and-forget text input to any session
- Optional press_enter flag (default: true)
- No marker wrapping, no output capture
- Use case: AI sends messages/commands without needing output back

Tab resize fix:
- Double requestAnimationFrame before fitAddon.fit() on tab switch
- Container has real dimensions after browser layout pass
- Also sends ssh_resize/pty_resize to backend with correct cols/rows
- Fixes 6-8 char wide terminals after switching tabs

Close confirmation:
- beforeunload event shows browser "Leave page?" dialog
- Only triggers if sessions are active
- Synchronous — cannot hang the close like onCloseRequested did

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:21:53 -04:00
Vantz Stockwell
5d472a6e53 fix: window close hanging — replace onCloseRequested with auto-save
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m50s
onCloseRequested async handler was permanently blocking window close
on Windows, even with the 2s timeout. The confirm() dialog and async
invoke chain prevented the close event from completing.

Fix: removed onCloseRequested entirely. Workspace now auto-saves
every 30 seconds via setInterval. Close button works immediately
with no handler blocking it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:46:23 -04:00
Vantz Stockwell
be76a61119 fix: update checker uses Gitea releases API instead of Tauri updater
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m53s
The Tauri native updater needs update.json hosted at a static URL.
Gitea packages don't support a 'latest' alias, so the endpoint
returned 'package does not exist'. Reverted Settings and startup
check to use check_for_updates command which queries the Gitea
releases API directly and works reliably.

The native auto-updater will work once we have proper static hosting
for update.json (or a redirect endpoint). For now, the manual check
+ download page approach is functional.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:16:42 -04:00
Vantz Stockwell
9f6085d251 perf: RDP optimizations — binary IPC, frame throttle, fast PNG
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 4m8s
1. Binary IPC: get_frame returns Vec<u8> directly instead of base64
   string. Eliminates 33% encoding overhead + string allocation +
   atob() decode on frontend. Frontend receives number[] from Tauri.

2. Frame throttle: reduced from ~30fps to ~20fps (every 3rd rAF tick).
   20% fewer frames with negligible visual difference for remote desktop.

3. Fast PNG compression: screenshot_png_base64 uses Compression::Fast
   for MCP screenshots, reducing encode time.

4. Dirty flag: already existed but documented — empty Vec returned when
   frame hasn't changed, frontend skips rendering.

Net effect: ~45% reduction in IPC bandwidth (no base64 overhead) +
20% fewer frame fetches + faster screenshot encoding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:54:25 -04:00
Vantz Stockwell
9c3afa39bd feat: Help menu + fix tab detach rendering
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m48s
Help menu (File → Help):
- Getting Started guide (connections, SFTP, copilot, tabs)
- Keyboard Shortcuts reference table
- MCP Integration page (setup command, all 18 tools documented,
  bridge path auto-populated, architecture explanation)
- About page with version and tech stack
- Opens as a tabbed popup window

Tab detach fixes:
- Added detached-*, editor-*, help-* to capabilities window list
  (detached windows had no event permissions — silent failure)
- SessionContainer filters out detached sessions (active=false)
  so the main window stops rendering the terminal when detached
- Terminal now only renders in the detached popup window

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:07:45 -04:00
Vantz Stockwell
beac33614a fix: window close hanging + add confirmation prompt
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m53s
The onCloseRequested async handler was blocking window close when
save_workspace invoke hung or threw. Fixed with:
1. Confirmation dialog: "Are you sure you want to close Wraith?"
   (only shown if sessions are active, cancel prevents close)
2. Workspace save wrapped in Promise.race with 2s timeout so a
   stuck invoke can never block the close indefinitely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:45:11 -04:00
Vantz Stockwell
d39c0d38ed fix: local PowerShell garbled output + resize not propagating
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 4m10s
Two issues:
1. convertEol was false for PTY sessions, but Windows ConPTY sends
   bare \n without \r. Now enabled on Windows PTY sessions (checked
   via navigator.platform). Unix PTY still false (driver handles it).

2. LocalTerminalView had no ResizeObserver, so the terminal never
   reflowed when the container size changed. Added ResizeObserver
   matching the SSH TerminalView pattern. Also added proper cleanup
   on unmount.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:38:24 -04:00
Vantz Stockwell
3638745436 feat: tab detach/reattach — pop sessions into separate windows
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 4m3s
Right-click any tab → "Detach to Window" opens the session in its
own Tauri window. The tab dims (opacity + italic) while detached.
Close the detached window → session reattaches to the main tab bar.

Architecture:
- DetachedSession.vue: standalone terminal that connects to the same
  backend session (SSH/PTY events keep flowing)
- App.vue detects #/detached-session?sessionId=X hash
- Tab context menu: Detach to Window, Close
- session:reattach event emitted on window close, main window listens
- Monitor bar included in detached SSH windows
- Session.active flag tracks detached state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:37:49 -04:00
Vantz Stockwell
0c6a4b8109 feat: Tauri auto-updater + RDP vault credentials + sidebar persist
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 2m55s
Tauri auto-updater:
- Signing pubkey in tauri.conf.json
- tauri-plugin-updater initialized in lib.rs
- CI workflow passes TAURI_SIGNING_PRIVATE_KEY env vars to cargo tauri build
- CI generates update.json manifest with signature and uploads to
  packages/latest/update.json endpoint
- Frontend checks for updates on startup via @tauri-apps/plugin-updater
- Downloads, installs, and relaunches seamlessly
- Settings → About button uses native updater too

RDP vault credentials:
- RDP connections now resolve credentials from vault via credentialId
- Same path as SSH: list_credentials → find by ID → decrypt_password
- Falls back to conn.options JSON if no vault credential linked
- Fixes blank username in RDP connect

Sidebar drag persist:
- reorder_connections and reorder_groups Tauri commands
- Batch-update sort_order in database on drop
- Order survives app restart

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:42:01 -04:00
Vantz Stockwell
10e0a6b196 fix: workspace restore crash — defer to setTimeout, wrap all imports
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m17s
Workspace restore was running synchronously in onMounted which could
crash if saved connection IDs were stale. The import of
@tauri-apps/api/window for onCloseRequested could also fail in
certain contexts.

Fix: defer restore to setTimeout(500ms) so the app renders first,
wrap each reconnect in individual try/catch, wrap the window close
listener setup in try/catch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:06:34 -04:00
Vantz Stockwell
1b74527a62 feat: tab notifications, session persistence, Docker panel, drag reorder sidebar
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m53s
Tab activity notifications:
- Background tabs pulse blue when new output arrives
- Clears when you switch to the tab
- useTerminal marks activity on every data event

Session persistence:
- Workspace saved to DB on window close (connection IDs + positions)
- Restored on launch — auto-reconnects saved sessions in order
- workspace_commands: save_workspace, load_workspace

Docker Manager (Tools → Docker Manager):
- Containers tab: list all, start/stop/restart/remove/logs
- Images tab: list all, remove
- Volumes tab: list all, remove
- One-click Builder Prune and System Prune buttons
- All operations via SSH exec channels — no Docker socket exposure

Sidebar drag-and-drop:
- Drag groups to reorder
- Drag connections between groups
- Drag connections within a group to reorder
- Blue border indicator on drop targets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:50:49 -04:00
Vantz Stockwell
357491b4e8 feat: debug logging macro + MCP tools inject button in copilot
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m58s
Debug logging:
- wraith_log!() macro available in all modules, writes to wraith.log
- SSH connect/auth, PTY spawn, RDP connect all log with session IDs
- MCP startup panic now shows the actual error message

Copilot "Tools" button:
- Shows when a PTY session is active in the copilot panel
- Injects a formatted list of all 18 MCP tools into the chat
- Groups tools by category: session, terminal, SFTP, network, utilities
- Includes parameter signatures so the AI knows how to call them

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:12:23 -04:00
Vantz Stockwell
4b26d9190b feat: update checker — startup prompt + Settings → About button
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m2s
Checks Gitea releases API for latest version on startup. If newer
version available, shows confirm dialog to open download page.

Also adds "Check for Updates" button in Settings → About with
version comparison, release notes display, and download button.

Backend: check_for_updates command with semver comparison (6 tests).
96 total tests, zero warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:35:54 -04:00
Vantz Stockwell
e9b504c733 fix: SFTP browser — default to / instead of /home, strip quotes from CWD
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m0s
/home doesn't exist on macOS (home dirs are /Users/). Changed default
SFTP path to / so it always loads. OSC 7 parser now strips stray
quotes from shell printf output that produced paths like /"/path".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:29:38 -04:00
Vantz Stockwell
99f46a2163 fix: remove unused variable in SshKeyGen
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m1s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:21:04 -04:00
Vantz Stockwell
6b5fad2289 feat: SSH key generator save-to-file buttons
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 6s
Save Private Key and Save Public Key buttons download the generated
keys as id_ed25519/id_rsa (private) and .pub (public) files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:15:15 -04:00
Vantz Stockwell
248381e4ce fix: remove dead EditorWindow import and editorFile ref
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m2s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:59:48 -04:00
Vantz Stockwell
3e548ed10e feat: SFTP editor opens as popup window instead of inline overlay
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 6s
Right-click → Edit now opens a separate Tauri window with the file
content in a monospace editor. Ctrl+S saves back to remote via SFTP.
Tab inserts 4 spaces. Modified indicator in toolbar.

Removed the inline EditorWindow overlay that covered the terminal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:58:09 -04:00
Vantz Stockwell
44c79decf3 fix: SFTP preserves position on tab switch + CWD following on macOS
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m58s
SFTP tab switch fix:
- Removed :key on FileTree that destroyed component on every switch
- useSftp now accepts a reactive Ref<string> sessionId
- Watches sessionId changes and reinitializes without destroying state
- Per-session path memory via sessionPaths map — switching back to a
  tab restores exactly where you were browsing

CWD following fix (macOS + all platforms):
- Injects OSC 7 prompt hook into the shell after SSH connect
- zsh: precmd() emits \e]7;file://host/path\e\\
- bash: PROMPT_COMMAND emits the same sequence
- Sent via the PTY channel so it configures the interactive shell
- The passive OSC 7 parser in the output loop picks it up
- SFTP sidebar auto-navigates to the current working directory

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:41:50 -04:00
Vantz Stockwell
f9c4e2af35 fix: launch presets wait for shell prompt before sending command
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m3s
Was sending the command after a blind 300ms delay with \n — too early
for PowerShell startup banner, and \n caused a blank line before the
command.

Fix: poll mcp_terminal_read every 200ms until a prompt is detected
($, #, %, >, PS>), then send the command with \r (carriage return,
not newline). Falls back to sending after 5s timeout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:37:35 -04:00
Vantz Stockwell
6f26822b85 fix: populate version in Settings → About from Tauri app config
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m58s
Used getVersion() from @tauri-apps/api/app which reads the version
from tauri.conf.json (patched by CI from the git tag).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:28:38 -04:00
Vantz Stockwell
ef377e8fe8 fix: PasswordGen TS error — navigator not available in Vue template scope
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m3s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:23:51 -04:00
Vantz Stockwell
b3f56a2729 feat: Tools R2 — DNS, Whois, Bandwidth, Subnet Calculator
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 7s
4 new tools with full backend + popup UIs:

DNS Lookup:
- dig/nslookup/host fallback chain on remote host
- Record type selector (A, AAAA, MX, NS, TXT, CNAME, SOA, SRV, PTR)

Whois:
- Remote whois query, first 80 lines
- Works for domains and IP addresses

Bandwidth Test (2 modes):
- iperf3: LAN speed test between remote host and iperf server
- Internet: speedtest-cli / curl-based Cloudflare test fallback

Subnet Calculator:
- Pure Rust, no SSH needed
- CIDR input with quick-select buttons (/8 through /32)
- Displays: network, broadcast, netmask, wildcard, host range,
  total/usable hosts, class, private/public

Tools menu now has 11 items across 3 sections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:12:11 -04:00
Vantz Stockwell
875dd1a28f feat: complete Tools suite — 7 tool UIs in popup windows
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 6s
All 7 tool windows with full UIs:
- Network Scanner: subnet scan, ARP+DNS discovery, results table with
  Quick Scan per host, SSH/RDP connect buttons, CSV export
- Port Scanner: custom range or quick scan (24 common ports), open/closed
  results table with service names
- Ping: remote ping with count, raw output display
- Traceroute: remote traceroute, raw output display
- Wake on LAN: MAC address input, broadcasts via python3 on remote host
- SSH Key Generator: ed25519/RSA, copy public/private key, fingerprint
- Password Generator: configurable length/charset, copy button, history

Architecture:
- App.vue detects tool mode via URL hash (#/tool/name?sessionId=...)
- ToolWindow.vue routes to correct tool component
- Tools menu in toolbar opens Tauri popup windows (WebviewWindow)
- Capabilities grant tool-* windows the same permissions as main
- SFTP context menu: right-click Edit/Download/Rename/Delete

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:07:15 -04:00
Vantz Stockwell
5cc412a251 feat: Tools menu + backend commands for all tools
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m2s
Tools menu in toolbar (next to File) with 7 tools:
- Network Scanner (existing scan_network command)
- Port Scanner (existing scan_ports/quick_scan commands)
- Ping — via SSH exec channel, cross-platform
- Traceroute — via SSH exec channel
- Wake on LAN — broadcasts WoL magic packet via python3 on remote host
- SSH Key Generator — pure Rust ed25519/RSA keygen via ssh-key crate
- Password Generator — cryptographic random with configurable charset

Backend: all 5 new Tauri commands (tool_ping, tool_traceroute,
tool_wake_on_lan, tool_generate_ssh_key, tool_generate_password)

Frontend: Tools dropdown menu wired, popup window launcher ready.
Tool window UIs (the actual panels inside each popup) to follow.

SFTP context menu: right-click Edit/Download/Rename/Delete working.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:03:34 -04:00
Vantz Stockwell
2d0964f6b2 feat: network scanner + SFTP context menu + CI fix
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m7s
Network scanner (through SSH exec channels):
- scan_network: ping sweep + ARP table + reverse DNS on remote network
- scan_ports: TCP connect scan via bash /dev/tcp (parallel batches of 20)
- quick_scan: 24 common ports (SSH, HTTP, RDP, SMB, DB, etc.)
- Cross-platform: Linux + macOS
- No agent/nmap required — uses standard POSIX commands
- All scans run on the remote host through existing SSH tunnel

SFTP context menu:
- Right-click on files/folders shows Edit, Download, Rename, Delete
- Right-click on folders shows Open Folder
- Teleport menu to body for proper z-index layering
- Click-away handler to close menu
- Rename uses sftp_rename invoke

CI fix:
- Added default-run = "wraith" to Cargo.toml
- The [[bin]] entry for wraith-mcp-bridge confused Cargo about which
  binary is the Tauri app main binary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 23:56:42 -04:00
Vantz Stockwell
4532f3beb6 feat: local terminal tabs — + button spawns WSL/Git Bash/PowerShell/CMD
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 16s
The + button in the tab bar now shows a dropdown of detected local
shells. Clicking one opens a full-size PTY terminal in the main
content area as a proper tab — not the copilot sidebar.

- New "local" protocol type in Session interface
- LocalTerminalView component uses useTerminal(id, 'pty')
- SessionContainer renders local sessions alongside SSH/RDP
- TabBadge shows purple dot for local sessions
- Shell detection includes WSL (wsl.exe) on Windows
- closeSession handles PTY disconnect for local tabs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 23:46:09 -04:00
Vantz Stockwell
2ad6da43eb feat: remote monitoring bar + SFTP tab follow + CWD macOS fix
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 15s
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>
2026-03-24 23:38:01 -04:00
Vantz Stockwell
216cd0cf34 feat: copilot QoL batch — resizable, SFTP, context, errors, presets
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 15s
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>
2026-03-24 23:32:49 -04:00
Vantz Stockwell
bc608b0683 feat: copilot QoL — resizable panel, SFTP tools, context, error watcher
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 15s
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>
2026-03-24 23:30:12 -04:00
Vantz Stockwell
5fc8951334 fix: draggable tabs — use div instead of button, set dataTransfer data
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m59s
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>
2026-03-24 21:35:30 -04:00
Vantz Stockwell
02b3ee053d feat: connection status indicators + draggable tab reordering
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m52s
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>
2026-03-24 21:30:20 -04:00
Vantz Stockwell
087b00c886 ui: double copilot panel width to 640px for ultrawide displays
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m49s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:24:13 -04:00
Vantz Stockwell
512347af5f feat: Local PTY Copilot Panel — replace Gemini stub with real terminal
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m6s
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>
2026-03-24 20:43:18 -04:00
Vantz Stockwell
f825692ecc feat: SSH key file browser — Browse button to load keys from disk
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m54s
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>
2026-03-24 19:20:00 -04:00
Vantz Stockwell
99ecbe739e feat: RDP clipboard paste, keyboard grab default ON, frame dirty flag
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m49s
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>
2026-03-24 17:57:16 -04:00
Vantz Stockwell
2dfe4f9d7a fix: focus terminal on mount so keyboard input works immediately
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Has been cancelled
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>
2026-03-24 17:47:16 -04:00
Vantz Stockwell
76c5ddbfb5 fix: correct parameter name mismatches in sidebar CRUD invoke calls
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m47s
Tauri v2 matches JS keys to Rust parameter names. The frontend was
sending connectionId/groupId but Rust expects id. Fixed:
- delete_connection: connectionId → id
- delete_group: groupId → id
- rename_group: groupId → id
- create_group: parentId → parent_id

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:20:47 -04:00
Vantz Stockwell
9aa911f7b8 fix: complete truncated GeminiPanel template — add chat UI and input bindings
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m6s
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>
2026-03-24 16:41:31 -04:00