use crate::ui::{self, Tone};
use super::{can_line, echo_cmd, meta, rule};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyType {
Fleet,
External,
CI,
}
#[derive(Debug, Clone, Default)]
pub struct MintCaps {
pub shell: bool,
pub write: bool,
pub all_ports: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Reuse {
Once,
Times(u32),
Reusable,
}
#[derive(Debug, Clone)]
pub struct Lifetime {
pub ttl: String, pub reuse: Reuse,
pub max_ttl: String, }
pub fn render_header() -> String {
format!(
" {}",
ui::paint(Tone::Brand, "filament mint — a key that lets a machine join, scoped and expiring")
)
}
pub fn render_key_type_picker() -> String {
let fleet = format!(
" {} {}",
ui::paint(Tone::Brand, ui::glyph_fleet()),
ui::paint(Tone::Bold, "(o) A device in my fleet")
);
let fleet_desc = ui::paint(Tone::Dim, " my own laptop / box / CI — permissive within scope");
let external = format!(
" {} {}",
ui::paint(Tone::Warn, ui::glyph_extern()),
ui::paint(Tone::Dim, "( ) An external share")
);
let external_desc = ui::paint(Tone::Dim, " give someone else narrow, time-boxed access");
let ci = format!(
" {}",
ui::paint(Tone::Dim, "( ) A CI / automation runner")
);
let ci_desc = ui::paint(Tone::Dim, " single-use, pinned to one machine, then forgotten");
format!(
"What is this key for?\n{fleet}\n{fleet_desc}\n{external}\n{external_desc}\n{ci}\n{ci_desc}"
)
}
pub fn render_summary(key_type: KeyType, caps: &MintCaps) -> String {
let mut lines = vec![format!(" {}", ui::paint(Tone::Dim, rule()))];
match key_type {
KeyType::Fleet => {
lines.push(can_line(true, false, "drop files in your inbox · reach your exposed ports · read ~/share"));
if caps.shell {
lines.push(can_line(true, true, "open a shell — a real terminal on this machine, running as you"));
} else {
lines.push(can_line(false, false, "open a shell"));
}
if caps.write {
lines.push(can_line(true, true, "write to mounted dirs — can change or delete your files"));
} else {
lines.push(can_line(false, false, "write to disk"));
}
if caps.all_ports {
lines.push(can_line(true, true, "reach ALL ports — not just the ports you chose to expose"));
} else {
lines.push(can_line(false, false, "join your mesh"));
}
}
KeyType::External => {
lines.push(can_line(true, false, "send you files"));
lines.push(meta(" (the only thing on, until you add more)"));
lines.push(can_line(false, false, "read your files · open a shell · reach any port · join your mesh"));
lines.push(meta(" one-way (them → you) · expires in 1h · no auto-renew · not your fleet"));
}
KeyType::CI => {
lines.push(can_line(true, false, "run one job on ci-box, then vanish"));
lines.push(can_line(false, false, "persist · open a shell · reach other ports · join your mesh"));
lines.push(meta(" single-use · pinned to ci-box · expires in 15m · ephemeral (no identity left behind)"));
}
}
lines.push(format!(" {}", ui::paint(Tone::Dim, rule())));
lines.join("\n")
}
pub fn render_deliberate_access(caps: &MintCaps, pending_token: Option<&str>) -> String {
let border = format!(
" {} {} {}",
ui::paint(Tone::Warn, ui::glyph_warn()),
ui::paint(Tone::Warn, "DELIBERATE ACCESS — off unless you turn it on"),
ui::paint(Tone::Dim, rule())
);
let shell_row = if caps.shell {
format!(
" │ {} open a shell type {} to keep it on: [ {}▌ ]",
ui::paint(Tone::Warn, "[x]"),
ui::paint(Tone::Warn, "SHELL"),
pending_token.unwrap_or("")
)
} else {
" │ [ ] open a shell a real terminal on this machine, running as you".to_string()
};
let write_row = if caps.write {
format!(
" │ {} write to mounted dirs type {} to keep it on: [ {}▌ ]",
ui::paint(Tone::Warn, "[x]"),
ui::paint(Tone::Warn, "WRITE"),
pending_token.unwrap_or("")
)
} else {
" │ [ ] write to mounted dirs can change or delete your files".to_string()
};
let ports_row = if caps.all_ports {
format!(
" │ {} reach ALL ports type {} to keep it on: [ {}▌ ]",
ui::paint(Tone::Warn, "[x]"),
ui::paint(Tone::Warn, "ALL-PORTS"),
pending_token.unwrap_or("")
)
} else {
" │ [ ] reach ALL ports not just the ports you chose to expose".to_string()
};
let footer = format!(" └{}", ui::paint(Tone::Dim, rule()));
format!("{border}\n{shell_row}\n{write_row}\n{ports_row}\n{footer}")
}
pub fn render_lifetime(lifetime: &Lifetime) -> String {
let reuse_label = match &lifetime.reuse {
Reuse::Once => format!("{} once", ui::paint(Tone::Dim, "(o)")),
Reuse::Times(n) => format!("{} {n} times", ui::paint(Tone::Dim, "( )")),
Reuse::Reusable => format!(
"{} reusable {}",
ui::paint(Tone::Dim, "( )"),
ui::paint(Tone::Warn, ui::glyph_warn())
),
};
let mut lines = vec![
format!(" {}", ui::paint(Tone::Dim, rule())),
format!(" Expires in: [ {} ] ◀────●───────▶ (max {} for this key type)", lifetime.ttl, lifetime.max_ttl),
format!(" Reuse: {reuse_label}"),
];
if lifetime.reuse != Reuse::Once {
lines.push(format!(
" {}",
ui::paint(Tone::Warn, "\"5 times\" is best-effort hygiene, not a guarantee — a copied key can be")
));
lines.push(format!(
" {}",
ui::paint(Tone::Warn, "claimed again at a peer that never saw the count.")
));
lines.push(format!(
" {}",
ui::paint(Tone::Dim, "To make single-use real, pin the machine: --audience ci-box → enforced there.")
));
}
lines.join("\n")
}
pub fn render_completion(code: &str, key_type: KeyType, caps: &MintCaps, lifetime: &Lifetime) -> String {
let type_label = match key_type {
KeyType::Fleet => ui::paint(Tone::Brand, "fleet key"),
KeyType::External => ui::paint(Tone::Warn, "external key"),
KeyType::CI => ui::paint(Tone::Dim, "CI key"),
};
let caps_str = {
let mut parts = vec![];
if caps.shell { parts.push("shell"); }
if caps.write { parts.push("write"); }
if caps.all_ports { parts.push("all-ports"); }
if parts.is_empty() { String::new() } else { format!(" · {}", parts.join(", ")) }
};
let reuse_str = match &lifetime.reuse {
Reuse::Once => "once",
Reuse::Times(n) => match *n {
5 => "5 times",
_ => "N times",
},
Reuse::Reusable => "reusable",
};
let cmd = format!("filament mint --{} --ttl {} --reuse {}{}",
match key_type { KeyType::Fleet => "fleet", KeyType::External => "external", KeyType::CI => "ci" },
lifetime.ttl,
reuse_str,
if caps.shell { " --allow shell" } else { "" },
);
format!(
"{ok} {minted}\n\n {code}\n\n{meta}\n{echo}",
ok = ui::paint(Tone::Ok, ui::glyph_ok()),
minted = "Minted. Share this with the machine that's joining:",
code = ui::paint(Tone::Bold, &format!("filament join {code}")),
meta = meta(&format!("{type_label} · {reuse_str} · expires in {} · {caps_str}", lifetime.ttl)),
echo = echo_cmd(&cmd),
)
}
pub fn err_shell_not_flag() -> (String, i32) {
(
format!(
"{err}\n {fix}",
err = ui::paint(Tone::Err, &format!("{} --shell is not a flag. Shell is deliberate access.", ui::glyph_err())),
fix = ui::paint(Tone::Dim, "To grant it on purpose: filament mint --fleet --ttl 1h --allow shell"),
),
super::EXIT_BAD_ARG,
)
}
pub fn err_needs_key_type() -> (String, i32) {
(
format!(
"{err}\n{fix}",
err = ui::paint(Tone::Err, &format!("{} mint needs a key type in non-interactive mode.", ui::glyph_err())),
fix = ui::paint(Tone::Dim, "filament mint --fleet | --external <peer> | --ci\n(add --ttl; for external, at least one --allow)"),
),
super::EXIT_BAD_ARG,
)
}
pub fn err_yes_no_deliberate() -> (String, i32) {
(
format!(
"{err}\n{fix}",
err = ui::paint(Tone::Err, &format!("{} --yes will not enable a deliberate option you didn't name.", ui::glyph_err())),
fix = ui::paint(Tone::Dim, "Say it explicitly: filament mint --fleet --ttl 1h --allow reuse --yes"),
),
super::EXIT_BAD_ARG,
)
}
pub fn err_over_ttl() -> (String, i32) {
(
format!(
"{err}\n{fix}",
err = ui::paint(Tone::Err, &format!("{} external keys expire within 24h (this key type's ceiling).", ui::glyph_err())),
fix = ui::paint(Tone::Dim, "Pick a shorter --ttl, or re-mint when it lapses. Long-lived trust is `pair`, not a key."),
),
super::EXIT_REFUSED,
)
}
pub fn err_mesh_not_grantable() -> (String, i32) {
(
format!(
"{err}\n{fix}",
err = ui::paint(Tone::Err, &format!("{} mesh is never grantable by a key — a runner or borrower can't join your L3 overlay.", ui::glyph_err())),
fix = ui::paint(Tone::Dim, "(Refused at the verifier regardless of signature. Not a flag; there's no override.)"),
),
super::EXIT_REFUSED,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fleet_summary_defaults() {
let caps = MintCaps::default();
let summary = render_summary(KeyType::Fleet, &caps);
assert!(summary.contains("drop files in your inbox"), "fleet default must show can-drop-files");
assert!(summary.contains("open a shell"), "must mention shell");
assert!(summary.contains("write to disk"), "must mention write");
assert!(summary.contains("join your mesh"), "must mention mesh");
}
#[test]
fn fleet_summary_shell_on() {
let caps = MintCaps { shell: true, write: false, all_ports: false };
let summary = render_summary(KeyType::Fleet, &caps);
assert!(summary.contains("open a shell — a real terminal"), "shell-on must show deliberate description");
}
#[test]
fn external_summary() {
let caps = MintCaps::default();
let summary = render_summary(KeyType::External, &caps);
assert!(summary.contains("send you files"), "external must show send");
assert!(summary.contains("read your files"), "external must block read");
assert!(summary.contains("no auto-renew"), "external must note no auto-renew");
}
#[test]
fn ci_summary() {
let caps = MintCaps::default();
let summary = render_summary(KeyType::CI, &caps);
assert!(summary.contains("run one job"), "CI must show run-one-job");
assert!(summary.contains("single-use"), "CI must note single-use");
assert!(summary.contains("ephemeral"), "CI must note ephemeral");
}
#[test]
fn confirm_mistype_render() {
let s = super::super::confirm_mistype("SHELL");
assert!(s.contains("SHELL"), "mistype must reference the token");
assert!(s.contains("left off"), "mistype must say left off");
}
#[test]
fn err_shell_not_flag_exit_code() {
let (msg, code) = err_shell_not_flag();
assert!(msg.contains("--shell"), "must reference the flag");
assert!(msg.contains("deliberate access"), "must explain why");
assert_eq!(code, 2, "bad-flag = exit 2");
}
#[test]
fn err_needs_key_type_exit_code() {
let (msg, code) = err_needs_key_type();
assert!(msg.contains("non-interactive"), "must mention non-interactive");
assert_eq!(code, 2, "missing-arg = exit 2");
}
#[test]
fn err_over_ttl_exit_code() {
let (_, code) = err_over_ttl();
assert_eq!(code, 1, "refused-by-model = exit 1");
}
#[test]
fn err_mesh_not_grantable_exit_code() {
let (msg, code) = err_mesh_not_grantable();
assert!(msg.contains("mesh"), "must mention mesh");
assert_eq!(code, 1, "refused-by-model = exit 1");
}
#[test]
fn completion_render() {
let caps = MintCaps { shell: true, write: false, all_ports: false };
let lt = Lifetime { ttl: "1h".into(), reuse: Reuse::Once, max_ttl: "24h".into() };
let s = render_completion("clever-lynx-63-brave-otter", KeyType::Fleet, &caps, <);
assert!(s.contains("filament join clever-lynx-63-brave-otter"), "must show join command");
assert!(s.contains("fleet key"), "must label as fleet");
assert!(s.contains("once"), "must show reuse");
assert!(s.contains("shell"), "must show granted caps");
}
#[test]
fn header_is_brand() {
let h = render_header();
assert!(h.contains("filament mint"), "header must contain filament mint");
}
}