use crate::ui::{self, Tone};
use super::{double_rule, echo_cmd, meta};
use filament_pair::words::SpokenSas;
pub fn render_same_person_banner(device_name: &str) -> String {
format!(
"{rule}\n{banner}\n{rule}\n\n{msg}\n\n{will}\n{wont}\n{meta}\n\n{btns}",
rule = ui::paint(Tone::Brand, double_rule()),
banner = format!(" {} SAME PERSON / this is you", ui::paint(Tone::Brand, ui::glyph_fleet())),
msg = format!(" \"{}\" is signed by your identity. Adding it to your fleet.", device_name),
will = format!(
" This device will: {}",
ui::paint(Tone::Ok, "send/receive with your fleet / reach exposed ports / read the configured share root")
),
wont = format!(
" It will not: {}",
ui::paint(Tone::Dim, "owner-equivalent shell / write to disk (grant those later if you want)")
),
meta = meta(&format!("{} fleet / cryptographically Proven", ui::glyph_fleet())),
btns = format!(
" {} {}",
ui::paint(Tone::Bold, "[ Add to my fleet ]"),
ui::paint(Tone::Dim, "[ Cancel ]")
),
)
}
pub fn render_same_person_success(device_name: &str) -> String {
format!(
"{ok} {name} joined your fleet.\n{echo}",
ok = ui::paint(Tone::Ok, ui::glyph_ok()),
name = device_name,
echo = echo_cmd("filament add"),
)
}
pub fn render_someone_else_banner() -> String {
format!(
"{rule}\n{banner}\n{rule}\n\n{msg}",
rule = ui::paint(Tone::Warn, double_rule()),
banner = format!(" {} SOMEONE ELSE / not your identity", ui::paint(Tone::Warn, ui::glyph_extern())),
msg = ui::paint(Tone::Dim, " This is not you. Nothing is shared yet; you decide exactly what, and for how long."),
)
}
pub fn render_pake_words(sas: &SpokenSas) -> String {
let words_str = sas.words().join(" · ");
format!(
"{prompt}\n\n {words}\n\n{confirm}\n{fingerprint}",
prompt = " Say these three words out loud. They must hear the SAME three, in this order.",
words = ui::paint(Tone::Bold, &words_str),
confirm = format!(
" Do they hear exactly these?\n {} {}",
ui::paint(Tone::Bold, "[ Yes, they match ]"),
ui::paint(Tone::Err, "[ No / stop ]")
),
fingerprint = ui::paint(Tone::Dim, " Fingerprint 7f3a 9c21 4b... [compare] / informational; the words are the trust."),
)
}
pub fn render_pake_mismatch() -> String {
format!(
"{err} Stopped. If the words didn't match, someone may be in the middle. Don't retry\n on the same channel. Get a fresh code from them and try again.",
err = ui::paint(Tone::Err, ui::glyph_err()),
)
}
pub fn render_inter_user_form(peer_name: &str) -> String {
format!(
"{ok} Words matched. What may \"{peer_name}\" do, and for how long?\n\n\
{send} {port}\n\
{read} {shell}\n\
Direction: {dir_out} {dir_both}\n\
Ends in: [ 1h ] <---o-----> (max 24h)\n\n\
{rule}\n\
{can}\n\
{cannot}\n\
{meta}\n\n\
{grant} {cancel}",
ok = ui::paint(Tone::Ok, ui::glyph_ok()),
send = " [ ] send me files",
port = "[ ] reach my port [ 8080 ]",
read = " [ ] read-only ~/share",
shell = format!("[ ] OWNER-EQUIVALENT shell (can act as you) {}", ui::paint(Tone::Warn, ui::glyph_warn())),
dir_out = ui::paint(Tone::Dim, "(o)"),
dir_both = format!("{} both ways", ui::paint(Tone::Dim, "( )")),
rule = super::rule(),
can = format!(" {} {}: send files", ui::paint(Tone::Ok, ui::glyph_ok()), peer_name),
cannot = format!(
" {} you -> {}: nothing / {} cannot shell, mount, or reach other ports",
ui::paint(Tone::Err, ui::glyph_err()),
peer_name,
peer_name
),
meta = meta(&format!("{} external / one-way / expires 14:41 (in 1h) / no auto-renew", ui::glyph_extern())),
grant = ui::paint(Tone::Bold, "[ Grant ]"),
cancel = ui::paint(Tone::Dim, "[ Cancel ]"),
)
}
pub fn render_inter_user_success(peer_name: &str, cap: &str, expiry: &str) -> String {
let echo = match cap {
"shell" | "transfer" | "mount" => {
echo_cmd(&format!("filament grant {peer_name} {cap} --for {expiry}"))
}
_ => String::new(),
};
format!(
"{ok} {peer_name} can {cap_label} until {expiry}. It ends on its own; no cleanup needed.\n{echo}",
ok = ui::paint(Tone::Ok, ui::glyph_ok()),
cap_label = if cap == "shell" { "act as you through a shell (OWNER-EQUIVALENT)" } else { cap },
echo = echo,
)
}
pub fn err_pair_interactive() -> (String, i32) {
(
format!(
"{err} add is interactive (it needs consent on both ends). For automation, create a bounded invitation instead:\n {fix}",
err = ui::paint(Tone::Err, ui::glyph_err()),
fix = "filament invite",
),
super::EXIT_BAD_ARG,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same_person_banner_fleet_glyph() {
let s = render_same_person_banner("pixel-7");
assert!(s.contains("SAME PERSON"), "must show SAME PERSON");
assert!(s.contains("pixel-7"), "must show device name");
assert!(s.contains("fleet") || s.contains("[fleet]"), "must show fleet glyph");
}
#[test]
fn someone_else_banner_extern_glyph() {
let s = render_someone_else_banner();
assert!(s.contains("SOMEONE ELSE"), "must show SOMEONE ELSE");
assert!(s.contains("[extern]") || s.contains("○"), "must show extern glyph");
assert!(s.contains("not your identity"), "must show not-your-identity");
}
#[test]
fn pake_mismatch_honest_copy() {
let s = render_pake_mismatch();
assert!(s.contains("someone may be in the middle"), "must explain the risk");
assert!(s.to_lowercase().contains("don't retry"), "must advise against retry");
}
#[test]
fn inter_user_form_deliberate_glyph() {
let s = render_inter_user_form("carol");
assert!(s.contains("carol"), "must show peer name");
assert!(s.contains("shell") || s.contains("SHELL"), "must mention shell");
assert!(s.contains("⚠") || s.contains("!"), "shell row must carry warn glyph");
}
#[test]
fn inter_user_success() {
let s = render_inter_user_success("carol", "send files", "14:41 (1h)");
assert!(s.contains("carol"), "must show peer name");
assert!(s.contains("send files"), "must show capability");
assert!(s.contains("14:41"), "must show expiry");
}
#[test]
fn err_pair_interactive_exit_code() {
let (msg, code) = err_pair_interactive();
assert!(msg.contains("interactive"), "must explain why");
assert!(msg.contains("invite"), "must suggest alternative");
assert_eq!(code, 2, "bad-arg = exit 2");
}
#[test]
fn same_person_success() {
let s = render_same_person_success("pixel-7");
assert!(s.contains("pixel-7"), "must show device name");
assert!(s.contains("joined your fleet"), "must confirm fleet join");
}
}