using System.Diagnostics.CodeAnalysis; namespace DodoSSH.Client.Domain; /// /// A saved command, decrypted. /// /// /// /// is the field this type exists to get right. A terminal is one input /// stream with no notion of "at a prompt": the remote may be inside vi, or at a sudo password /// prompt with echo off, and without shell integration the client cannot tell. So inserting a snippet is /// always "type this into whatever is there", never "run this command" — and whether a newline follows the /// text is the difference between the user reading what appeared and deciding, and something happening. /// It defaults to , which makes that decision the user's Enter key. /// /// /// is stored verbatim. No trimming, no newline normalisation — the same rule /// follows, for a related reason: a heredoc's trailing newline is /// load-bearing, and a shell that receives a here-document terminator with the whitespace tidied off it hangs /// waiting for one that never comes. /// /// /// Deliberately not in this version, each with a reason rather than an omission: host scoping, which /// needs a set merge that does not have; tags, which are their own reserved /// item kind; and parameter substitution, which would make this a template language expanding into a /// root shell — a second security surface for a feature whose first one is already the hard part. /// /// public sealed record SnippetSecret : IVaultSecret { /// What the snippet is called. The only name it has anywhere. public required string Label { get; init; } /// The text to insert. May be several lines. public required string Command { get; init; } /// Free-text notes. public string? Notes { get; init; } /// /// Whether inserting this also presses Enter. /// /// /// Off unless the user turns it on, per snippet. A vault-wide preference was the alternative and it is /// worse: the setting belongs to the command, because ls -la and rm -rf /var/lib/postgresql /// do not want the same answer, and a single switch would eventually be left on by whoever needed it for /// the first of those. /// public bool RunsOnInsert { get; init; } /// Whether this is storable, and why not if it is not. /// /// is checked for being blank but for nothing else. What makes a valid command is /// the remote shell's business, this client does not know which shell that is, and a validator guessing /// at it would refuse the legitimate cases — a bare \x03, a partial line meant to be completed by /// hand — while catching nothing that matters. /// public bool TryValidate([NotNullWhen(false)] out string? reason) { if (string.IsNullOrWhiteSpace(Label)) { reason = "A snippet needs a name."; return false; } if (string.IsNullOrEmpty(Command)) { reason = "A snippet needs something to insert."; return false; } reason = null; return true; } }