use std::fmt;
use std::io::{self, BufRead, Write as _};
use crate::truncate::truncate_with_suffix;
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum RiskLevel {
Safe,
Mutating,
Dangerous,
}
impl fmt::Display for RiskLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RiskLevel::Safe => write!(f, "safe"),
RiskLevel::Mutating => write!(f, "mutating"),
RiskLevel::Dangerous => write!(f, "dangerous"),
}
}
}
impl RiskLevel {
pub fn icon(&self) -> &'static str {
match self {
RiskLevel::Safe => "โน๏ธ ",
RiskLevel::Mutating => "๐",
RiskLevel::Dangerous => "โ ๏ธ ",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub enum ApproveMode {
Auto,
#[default]
Ask,
Strict,
}
impl ApproveMode {
pub fn parse(s: &str) -> Self {
match s.to_lowercase().as_str() {
"auto" => ApproveMode::Auto,
"strict" => ApproveMode::Strict,
_ => ApproveMode::Ask,
}
}
pub fn next(&self) -> Self {
match self {
ApproveMode::Ask => ApproveMode::Auto,
ApproveMode::Auto => ApproveMode::Strict,
ApproveMode::Strict => ApproveMode::Ask,
}
}
pub fn to_u8(self) -> u8 {
match self {
ApproveMode::Auto => 0,
ApproveMode::Ask => 1,
ApproveMode::Strict => 2,
}
}
pub fn from_u8(v: u8) -> Self {
match v {
0 => ApproveMode::Auto,
2 => ApproveMode::Strict,
_ => ApproveMode::Ask,
}
}
}
impl fmt::Display for ApproveMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ApproveMode::Auto => write!(f, "auto"),
ApproveMode::Ask => write!(f, "ask"),
ApproveMode::Strict => write!(f, "strict"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApprovalAnswer {
Yes,
No,
Abort,
}
#[derive(Debug, Clone)]
pub struct ApprovalRequest {
pub tool_name: String,
pub risk_level: RiskLevel,
pub summary: String,
}
impl ApprovalRequest {
pub fn new(tool_name: &str, risk: RiskLevel, params: &Value) -> Self {
Self {
tool_name: tool_name.to_string(),
risk_level: risk,
summary: build_summary(tool_name, params),
}
}
}
fn build_summary(tool_name: &str, params: &Value) -> String {
match tool_name {
"write" => summary_write(params),
"edit" => summary_edit(params),
"multi_edit" => summary_multi_edit(params),
"bash" => summary_bash(params),
"todo_write" => "ๆดๆฐไปปๅกๆธ
ๅ".to_string(),
_ => format!("ๆง่กๅทฅๅ
ท: {}", tool_name),
}
}
fn summary_write(params: &Value) -> String {
let path = params["path"].as_str().unwrap_or("<unknown>");
format!("ๅๅ
ฅๆไปถ: {}", path)
}
fn summary_edit(params: &Value) -> String {
let path = params["path"].as_str().unwrap_or("<unknown>");
format!("็ผ่พๆไปถ: {}", path)
}
fn summary_multi_edit(params: &Value) -> String {
let path = params["path"].as_str().unwrap_or("<unknown>");
let count = params["edits"].as_array().map(|a| a.len()).unwrap_or(0);
format!("ๆน้็ผ่พๆไปถ: {} ({} ๅคไฟฎๆน)", path, count)
}
fn summary_bash(params: &Value) -> String {
let cmd = params["command"].as_str().unwrap_or("<unknown>");
let display_cmd = if cmd.len() > 120 {
truncate_with_suffix(cmd, 120)
} else {
cmd.to_string()
};
format!("ๆง่กๅฝไปค: {}", display_cmd)
}
pub fn needs_approval(mode: ApproveMode, risk: RiskLevel) -> bool {
match mode {
ApproveMode::Auto => false,
ApproveMode::Ask => risk >= RiskLevel::Mutating,
ApproveMode::Strict => true,
}
}
pub fn build_approval_request(tool_name: &str, risk: RiskLevel, params: &Value) -> ApprovalRequest {
ApprovalRequest::new(tool_name, risk, params)
}
pub fn prompt_approval(request: &ApprovalRequest) -> ApprovalAnswer {
println!();
println!("โโ ็กฎ่ฎค่ฏทๆฑ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
println!("โ {} {}", request.risk_level.icon(), request.summary);
println!("โ ้ฃ้ฉ็ญ็บง: {}", request.risk_level);
println!("โ");
println!("โ [y] ๆง่ก [n] ่ทณ่ฟ [a] ไธญๆญขๆฌ่ฝฎ");
println!("โโโโ๏ฟฝ๏ฟฝ๏ฟฝโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
print!("> ");
let _ = io::stdout().flush();
let answer = read_approval_answer();
println!();
answer
}
fn read_approval_answer() -> ApprovalAnswer {
let stdin = io::stdin();
let mut line = String::new();
if stdin.lock().read_line(&mut line).is_err() {
return ApprovalAnswer::No;
}
match line.trim().to_lowercase().as_str() {
"y" | "yes" | "" => ApprovalAnswer::Yes,
"n" | "no" => ApprovalAnswer::No,
"a" | "abort" | "q" | "quit" => ApprovalAnswer::Abort,
_ => ApprovalAnswer::Yes, }
}