using System.Text.RegularExpressions; namespace ClaudeDo.Data.Models; /// A server an interactive claude session can be started on via ssh; connection details live in the user's ssh config. public sealed record SshWorkspace(string Id, string Name, string Host, string RemotePath) { // A leading '-' would make ssh parse the alias as an option (-oProxyCommand=... runs local code). private static readonly Regex HostPattern = new(@"^[A-Za-z0-9._@][A-Za-z0-9._@-]*$", RegexOptions.Compiled); /// Returns the first invalid field ("name", "host", "path") or null when valid. public static string? Validate(SshWorkspace w) { if (string.IsNullOrWhiteSpace(w.Name)) return "name"; if (string.IsNullOrEmpty(w.Host) || !HostPattern.IsMatch(w.Host)) return "host"; var p = w.RemotePath ?? ""; if (!(p.StartsWith('/') || p.StartsWith('~')) || p.Contains('\'') || p.Contains('\n') || p.Contains('\r')) return "path"; return null; } /// Single-quotes a validated remote path for a POSIX shell, leaving a leading ~ unquoted so it still expands. public static string QuoteRemotePath(string path) { if (path == "~") return "~"; if (path.StartsWith("~/")) return "~/'" + path[2..] + "'"; return "'" + path + "'"; } }