ProcessOutput() in the readLoop was processing every byte of SSH output,
looking for OSC 7 sequences. When these sequences split across read
boundaries (common with 32KB buffer), partial sequences leaked through
and corrupted xterm.js parser state — producing red/green color blocks
instead of text, and characters rendering at wrong widths.
Reverted readLoop to direct passthrough (v0.7.3 behavior). Also removed
shell integration injection (stty -echo + PROMPT_COMMAND) which caused
terminal mode disruption on macOS. Also removed 4px xterm padding that
could cause fitAddon cell width miscalculation.
CWD tracking will be re-implemented via a separate SSH exec channel
that polls pwd without touching the terminal data stream.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two missing halves of CWD tracking:
1. Frontend: useSftp now listens for ssh:cwd:{sessionId} Wails events
and calls navigateTo() when followTerminal is enabled (default: on).
2. Backend: re-added shell integration injection with stty -echo to
suppress visible command output. Leading space keeps it out of
shell history. Handles both bash (PROMPT_COMMAND) and zsh (precmd).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The CWD tracking PROMPT_COMMAND/precmd injection wrote raw escape
sequences to stdin that echoed back to the user. Removed until we
have a non-echoing mechanism (e.g., second SSH channel or .bashrc
modification). CWD tracking still works passively for shells that
already emit OSC 7 sequences.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
U-1: Replace ssh.InsecureIgnoreHostKey() with TOFU (Trust On First Use) host
key verification via HostKeyStore. New keys auto-store, matching keys accept
silently, CHANGED keys reject with MITM warning. Added DeleteHostKey() for
legitimate re-key scenarios.
U-2: Wire CWDTracker per SSH session. readLoop() now processes OSC 7 escape
sequences, strips them from terminal output, and emits ssh:cwd:{sessionID}
Wails events on directory changes. Shell integration commands (bash/zsh
PROMPT_COMMAND) injected after connection.
U-3: Session manager now tracks all SSH and RDP sessions via CreateWithID()
which accepts the service-level UUID instead of generating a new one.
ConnectSSH, ConnectSSHWithPassword, ConnectRDP register sessions;
DisconnectSession and RDPDisconnect remove them. ConnectedAt timestamp set.
U-4: WorkspaceService instantiated in New(), clean shutdown flag managed on
startup/exit, workspace state auto-saved on every session open/close.
Frontend-facing proxy methods exposed: SaveWorkspace, LoadWorkspace,
MarkCleanShutdown, WasCleanShutdown, GetSessionCWD.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove naive-ui and @xterm/addon-webgl from frontend deps — neither is
imported anywhere in frontend/src; the entire UI is hand-rolled Tailwind
and the terminal uses only FitAddon/SearchAddon/WebLinksAddon (22 packages
removed, 0 vulnerabilities)
- Add 003_connection_history.sql migration — CREATE TABLE IF NOT EXISTS so
it is safe and idempotent on existing databases; tracks per-connection
session duration for frequency/history analytics
- Wire MobaConfImporter into the plugin registry in app.New() so the
registry is no longer empty at runtime; ImportMobaConf continues to call
the importer directly (GetImporter key is "MobaXTerm", not "mobaconf")
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three fixes:
1. Streaming TextDecoder: a single TextDecoder instance with {stream: true}
persists across events. Split multi-byte UTF-8 sequences at Go read()
boundaries are now buffered and decoded correctly across chunks.
2. requestAnimationFrame batching: incoming SSH data is accumulated and
flushed to xterm.js once per frame instead of on every Wails event.
Eliminates the laggy typewriter effect when output arrives in small
chunks (which is normal for SSH PTY output).
3. PTY baud rate: bumped TTY_OP_ISPEED/OSPEED from 14400 (modem speed)
to 115200. Some remote PTYs throttle output to match the declared rate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reads %USERPROFILE%\.claude\.credentials.json (or ~/.claude/.credentials.json),
extracts the access and refresh tokens, stores them encrypted in Wraith's vault.
Works when Wraith's own OAuth exchange fails. If Claude Code is authenticated
on the same machine, Wraith piggybacks on the existing token.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Code sends code=true param and requests all 5 scopes:
org:create_api_key user:profile user:inference user:sessions:claude_code
user:mcp_servers. Wraith was only requesting user:inference and missing
the code=true flag, which likely caused the token exchange rejection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI now creates a proper Gitea Release after uploading packages. The
updater queries /api/v1/repos/{owner}/{repo}/releases/latest which
requires a Release object (not just a tag). Previous tags won't have
releases — the updater will start working from this build forward.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Gitea's generic package list endpoint wasn't returning 200. Switched to
/api/v1/repos/{owner}/{repo}/releases/latest which is the standard
Gitea releases API. Download URLs still use the packages registry.
Repo is now public — no auth token needed for version checks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logs the raw API response body and status from Gitea package API,
plus parsed version count and current version comparison. This will
show exactly why updates aren't being detected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause of pubkey auth failure: SSH key credentials had no username,
so ConnectSSH defaulted to "root" and the server rejected the key.
The SSH key form in ConnectionEditDialog only had Name, PEM, Passphrase.
Added Username field between Name and PEM.
Delete your existing SSH key credentials and re-create them with the
correct username.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
slog now writes to wraith.log instead of stdout. Debug-level logging enabled.
ConnectSSH and UpdateConnection log credential resolution details. This lets
us diagnose the pubkey auth issue without needing a console window.
Check: %APPDATA%\Wraith\wraith.log after attempting a connection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sidebar automatically switches from Connections to SFTP tab when an SSH
session becomes active. Added slog debug output to ConnectSSH showing
credentialID, vault state, and loaded credential details to diagnose
pubkey auth failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The callback page now shows the real error message instead of a generic
"Failed to exchange" message. Token exchange tries JSON Content-Type first
(matching Claude Code's pattern) with form-encoded fallback. Full response
body logged for debugging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: frontend btoa() encoded the PEM before sending to Go []byte
parameter. Wails already base64-encodes []byte over JSON bridge, so the
vault stored base64(base64(pem)) — garbage. Fix: Go method now accepts
string, frontend sends raw PEM. Keys must be re-added after this update.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ConnectSSH returns NO_CREDENTIALS error when no credential is stored
- Frontend catches auth failures and prompts for username/password
- ConnectSSHWithPassword method for ad-hoc password auth
- Version loaded from Go backend (build-time -ldflags) in settings + unlock screen
- Connection errors shown as alert() instead of silent console.error
- Added UpdateService.CurrentVersion() and WraithApp.GetVersion()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete connection/group now calls real Go backend (was local array splice)
- Duplicate connection calls ConnectionService.CreateConnection
- Rename group calls new ConnectionService.RenameGroup method
- Added Group+ and Host+ buttons to sidebar header
- Vault change password wired to real unlock/create flow
- Export/import vault shows helpful path info instead of stub alert
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical path wired end-to-end:
- ConnectSSH on WraithApp resolves credentials from vault, builds auth methods
- SSH output handler emits Wails events (base64) to frontend
- useTerminal.ts forwards keystrokes to SSHService.Write, resize to SSHService.Resize
- useTerminal.ts listens for ssh:data:{sessionId} events and writes to xterm.js
- session.store.ts connect() calls real Go ConnectSSH, not mock
- useSftp.ts calls real SFTPService.List instead of hardcoded mock data
- SFTP client auto-registered on SSH connection via pkg/sftp
- DisconnectSession cleans up both SSH and SFTP
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all TODO stubs in frontend stores with real Wails Call.ByName
bindings. The app store now calls WraithApp.IsFirstRun/CreateVault/Unlock
so vault state persists across launches. The connection store loads from
ConnectionService.ListConnections/ListGroups instead of hardcoded mock
data. The import dialog calls a new WraithApp.ImportMobaConf method that
parses the file, creates groups and connections in SQLite, and stores
host keys. ConnectionEditDialog also uses real Go CRUD calls. MainLayout
loads connections on mount after vault unlock.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace mock responses in the XO copilot panel with real Wails binding
calls to the Go AIService backend:
- StartLogin now opens the browser via pkg/browser.OpenURL
- SendMessage returns ChatResponse (text + tool call results) instead of
bare error, fixing the tool-call accumulation bug in messageLoop
- Add GetModel/SetModel methods for frontend model switching
- Frontend useCopilot composable calls Go via Call.ByName from
@wailsio/runtime, with conversation auto-creation, auth checks, and
error display in the chat panel
- Store defaults to isAuthenticated=false; panel checks auth on mount
- CopilotSettings syncs model changes and logout to the backend
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add internal/updater package with UpdateService that queries the Gitea
generic-package API for newer releases, downloads the installer with
SHA256 verification, and launches it to apply the update. Includes
semver comparison (CompareVersions) and comprehensive test coverage
with httptest-based mock servers.
Wire UpdateService into WraithApp (app.go accepts version param) and
register as a Wails service in main.go. Frontend StatusBar shows a
blue pill notification when an update is available; SettingsModal About
section displays the current version and a "Check for Updates" button
with idle/checking/found/up-to-date/error states.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- OAuth PKCE flow for Max subscription auth (no API key needed)
- Claude API client with SSE streaming (Messages API v1)
- 16 tool definitions: terminal, SFTP, RDP, session management
- Tool dispatch router mapping to existing Wraith services
- Conversation manager with SQLite persistence
- Terminal output ring buffer for AI context
- RDP screenshot encoder (RGBA → JPEG with downscaling)
- Wired into Wails app as AIService
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>