Complete TUI coverage and security audit

This commit is contained in:
Hermes Agent
2026-08-10 11:39:45 +00:00
parent 71bbff934e
commit c2632656bc
10 changed files with 958 additions and 44 deletions

View File

@@ -4,6 +4,8 @@ use std::{error::Error, fmt};
use ironstorage::command::{CliAction, CommandRequest, GitRequest, OtpRequest, parse_from};
use crate::action::{ACTIONS, Action};
const HISTORY_LIMIT: usize = 100;
const ROOT_COMMANDS: &[&str] = &[
@@ -81,6 +83,256 @@ pub const COMMAND_COVERAGE: &[CommandCoverage] = &[
coverage("unlock the TUI", ":unlock"),
];
/// Executable feature matrix for the complete upstream pass and pass-otp
/// surface. `None` means the command prompt is the intentional keyboard UI;
/// otherwise the action registry supplies the default hotkey and help entry.
pub const TUI_COVERAGE: &[TuiCoverage] = &[
tui(
"initialize recipients",
"recipient form",
Some(Action::Initialize),
":init GPG-ID…",
),
tui(
"list a directory",
"password tree",
Some(Action::Activate),
":list [PATH]",
),
tui(
"show an entry",
"structured viewer",
Some(Action::Activate),
":show [ENTRY]",
),
tui(
"copy a selected entry line",
"clipboard feedback",
Some(Action::Copy),
":show --clip [LINE] ENTRY",
),
tui(
"show a selected entry line as QR",
"resizable QR popup",
None,
":show --qrcode [LINE] ENTRY",
),
tui(
"find names",
"incremental tree filter",
Some(Action::Filter),
":find TERM…",
),
tui(
"grep decrypted entries",
"decrypted result pane",
Some(Action::Grep),
":grep [OPTIONS] PATTERN",
),
tui(
"insert an entry",
"masked insert form",
Some(Action::InsertEntry),
":insert [OPTIONS] ENTRY",
),
tui(
"edit an entry",
"structured editor",
Some(Action::EditEntry),
":edit ENTRY",
),
tui(
"generate a password",
"generation form",
Some(Action::GenerateEntry),
":generate [OPTIONS] ENTRY [LENGTH]",
),
tui(
"present a generated password",
"generation presentation selector",
Some(Action::GenerateEntry),
":generate (--clip | --qrcode) ENTRY [LENGTH]",
),
tui(
"remove entries or directories",
"confirmed removal form",
Some(Action::RemoveEntry),
":remove [OPTIONS] ENTRY",
),
tui(
"move entries or directories",
"move form",
Some(Action::MoveEntry),
":move [OPTIONS] SOURCE DESTINATION",
),
tui(
"copy entries or directories",
"copy form",
Some(Action::CopyEntry),
":copy [OPTIONS] SOURCE DESTINATION",
),
tui("initialize Git", "Git dashboard", None, ":git init"),
tui("show Git status", "Git dashboard", None, ":git status"),
tui("show Git log", "Git dashboard", None, ":git log [OPTIONS]"),
tui(
"show Git diff",
"zeroizing Git detail pane",
None,
":git diff [PATH]…",
),
tui("stage Git paths", "Git dashboard", None, ":git add PATH…"),
tui(
"create a Git commit",
"Git dashboard",
None,
":git commit -m MESSAGE",
),
tui(
"manage Git remotes",
"Git dashboard",
None,
":git remote [COMMAND]",
),
tui(
"manage Git configuration",
"Git dashboard",
None,
":git config (--get KEY | KEY VALUE)",
),
tui(
"fetch Git remote",
"Git progress view",
None,
":git fetch [REMOTE]",
),
tui(
"pull Git remote",
"Git progress view",
Some(Action::GitPull),
":git pull [REMOTE] [BRANCH]",
),
tui(
"push Git remote",
"Git progress view",
Some(Action::GitPush),
":git push [REMOTE] [BRANCH]",
),
tui(
"synchronize Git remote",
"Git progress view",
None,
":git sync [REMOTE]",
),
tui(
"resolve Git conflicts locally",
"Git conflict view",
None,
":git resolve-local",
),
tui(
"resolve Git conflicts remotely",
"Git conflict view",
None,
":git resolve-remote",
),
tui(
"generate an OTP code",
"focused OTP field",
Some(Action::OtpCode),
":otp code [OPTIONS] ENTRY",
),
tui(
"copy an OTP code",
"clipboard feedback",
Some(Action::OtpCopyCode),
":otp code --clip ENTRY",
),
tui(
"insert an OTP entry",
"masked OTP insert form",
Some(Action::OtpInsert),
":otp insert [OPTIONS] [ENTRY]",
),
tui(
"append OTP data",
"masked OTP append form",
Some(Action::OtpAppend),
":otp append [OPTIONS] ENTRY",
),
tui(
"present an OTP URI",
"secret URI popup",
Some(Action::OtpUri),
":otp uri ENTRY",
),
tui(
"copy an OTP URI",
"clipboard feedback",
Some(Action::OtpCopyUri),
":otp uri --clip ENTRY",
),
tui(
"present an OTP URI as QR",
"resizable QR popup",
Some(Action::OtpQr),
":otp uri --qrcode ENTRY",
),
tui(
"validate an OTP URI",
"masked validation form",
Some(Action::OtpValidate),
":otp validate URI",
),
tui("show pass-otp version", "status line", None, ":otp version"),
tui(
"show help",
"contextual help overlay",
Some(Action::Help),
":help [TOPIC]",
),
tui("show version", "status line", None, ":version"),
tui("lock the TUI", "locked screen", Some(Action::Lock), ":lock"),
tui(
"unlock the TUI",
"locked screen",
Some(Action::Unlock),
":unlock",
),
];
const fn tui(
operation: &'static str,
element: &'static str,
action: Option<Action>,
command: &'static str,
) -> TuiCoverage {
TuiCoverage {
operation,
element,
action,
command,
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TuiCoverage {
pub operation: &'static str,
pub element: &'static str,
pub action: Option<Action>,
pub command: &'static str,
}
impl TuiCoverage {
pub fn default_hotkey(self) -> Option<&'static str> {
let action = self.action?;
ACTIONS
.iter()
.find(|spec| spec.action == action)
.and_then(|spec| spec.bindings.first())
.map(|binding| binding.display)
}
}
const fn coverage(operation: &'static str, command: &'static str) -> CommandCoverage {
CommandCoverage { operation, command }
}
@@ -852,6 +1104,35 @@ mod tests {
.any(|row| row.command.starts_with(command))
);
}
for command_row in COMMAND_COVERAGE {
assert!(
TUI_COVERAGE
.iter()
.any(|row| row.operation == command_row.operation),
"missing TUI coverage for {}",
command_row.operation
);
}
for (index, row) in TUI_COVERAGE.iter().enumerate() {
assert!(!row.operation.is_empty());
assert!(!row.element.is_empty());
assert!(row.command.starts_with(':'));
assert!(
!TUI_COVERAGE[index + 1..]
.iter()
.any(|other| other.operation == row.operation),
"duplicate TUI operation {}",
row.operation
);
if row.action.is_some() {
assert!(
row.default_hotkey().is_some(),
"{} has no registered default hotkey",
row.operation
);
}
}
}
#[test]