1use std::fmt;
9use std::io::{self, BufRead, Write as _};
10
11use crate::truncate::truncate_with_suffix;
12use serde_json::Value;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
20pub enum RiskLevel {
21 Safe,
23 Mutating,
25 Dangerous,
27}
28
29impl fmt::Display for RiskLevel {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 RiskLevel::Safe => write!(f, "safe"),
33 RiskLevel::Mutating => write!(f, "mutating"),
34 RiskLevel::Dangerous => write!(f, "dangerous"),
35 }
36 }
37}
38
39impl RiskLevel {
40 pub fn icon(&self) -> &'static str {
42 match self {
43 RiskLevel::Safe => "โน๏ธ ",
44 RiskLevel::Mutating => "๐",
45 RiskLevel::Dangerous => "โ ๏ธ ",
46 }
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
56pub enum ApproveMode {
57 Auto,
59 #[default]
61 Ask,
62 Strict,
64}
65
66impl ApproveMode {
67 pub fn parse(s: &str) -> Self {
68 match s.to_lowercase().as_str() {
69 "auto" => ApproveMode::Auto,
70 "strict" => ApproveMode::Strict,
71 _ => ApproveMode::Ask,
72 }
73 }
74
75 pub fn next(&self) -> Self {
77 match self {
78 ApproveMode::Ask => ApproveMode::Auto,
79 ApproveMode::Auto => ApproveMode::Strict,
80 ApproveMode::Strict => ApproveMode::Ask,
81 }
82 }
83
84 pub fn to_u8(self) -> u8 {
86 match self {
87 ApproveMode::Auto => 0,
88 ApproveMode::Ask => 1,
89 ApproveMode::Strict => 2,
90 }
91 }
92
93 pub fn from_u8(v: u8) -> Self {
95 match v {
96 0 => ApproveMode::Auto,
97 2 => ApproveMode::Strict,
98 _ => ApproveMode::Ask,
99 }
100 }
101}
102
103impl fmt::Display for ApproveMode {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 match self {
106 ApproveMode::Auto => write!(f, "auto"),
107 ApproveMode::Ask => write!(f, "ask"),
108 ApproveMode::Strict => write!(f, "strict"),
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum ApprovalAnswer {
120 Yes,
122 No,
124 Abort,
126}
127
128#[derive(Debug, Clone)]
134pub struct ApprovalRequest {
135 pub tool_name: String,
136 pub risk_level: RiskLevel,
137 pub summary: String,
138}
139
140impl ApprovalRequest {
141 pub fn new(tool_name: &str, risk: RiskLevel, params: &Value) -> Self {
143 Self {
144 tool_name: tool_name.to_string(),
145 risk_level: risk,
146 summary: build_summary(tool_name, params),
147 }
148 }
149}
150
151fn build_summary(tool_name: &str, params: &Value) -> String {
153 match tool_name {
154 "write" => summary_write(params),
155 "edit" => summary_edit(params),
156 "multi_edit" => summary_multi_edit(params),
157 "bash" => summary_bash(params),
158 "todo_write" => "ๆดๆฐไปปๅกๆธ
ๅ".to_string(),
159 _ => format!("ๆง่กๅทฅๅ
ท: {}", tool_name),
160 }
161}
162
163fn summary_write(params: &Value) -> String {
164 let path = params["path"].as_str().unwrap_or("<unknown>");
165 format!("ๅๅ
ฅๆไปถ: {}", path)
166}
167
168fn summary_edit(params: &Value) -> String {
169 let path = params["path"].as_str().unwrap_or("<unknown>");
170 format!("็ผ่พๆไปถ: {}", path)
171}
172
173fn summary_multi_edit(params: &Value) -> String {
174 let path = params["path"].as_str().unwrap_or("<unknown>");
175 let count = params["edits"].as_array().map(|a| a.len()).unwrap_or(0);
176 format!("ๆน้็ผ่พๆไปถ: {} ({} ๅคไฟฎๆน)", path, count)
177}
178
179fn summary_bash(params: &Value) -> String {
180 let cmd = params["command"].as_str().unwrap_or("<unknown>");
181 let display_cmd = if cmd.len() > 120 {
182 truncate_with_suffix(cmd, 120)
183 } else {
184 cmd.to_string()
185 };
186 format!("ๆง่กๅฝไปค: {}", display_cmd)
187}
188
189pub fn needs_approval(mode: ApproveMode, risk: RiskLevel) -> bool {
195 match mode {
196 ApproveMode::Auto => false,
197 ApproveMode::Ask => risk >= RiskLevel::Mutating,
198 ApproveMode::Strict => true,
199 }
200}
201
202pub fn build_approval_request(tool_name: &str, risk: RiskLevel, params: &Value) -> ApprovalRequest {
204 ApprovalRequest::new(tool_name, risk, params)
205}
206
207pub fn prompt_approval(request: &ApprovalRequest) -> ApprovalAnswer {
210 println!();
211 println!("โโ ็กฎ่ฎค่ฏทๆฑ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
212 println!("โ {} {}", request.risk_level.icon(), request.summary);
213 println!("โ ้ฃ้ฉ็ญ็บง: {}", request.risk_level);
214 println!("โ");
215 println!("โ [y] ๆง่ก [n] ่ทณ่ฟ [a] ไธญๆญขๆฌ่ฝฎ");
216 println!("โโโโ๏ฟฝ๏ฟฝ๏ฟฝโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
217 print!("> ");
218 let _ = io::stdout().flush();
219
220 let answer = read_approval_answer();
221 println!();
222 answer
223}
224
225fn read_approval_answer() -> ApprovalAnswer {
227 let stdin = io::stdin();
228 let mut line = String::new();
229 if stdin.lock().read_line(&mut line).is_err() {
230 return ApprovalAnswer::No;
231 }
232 match line.trim().to_lowercase().as_str() {
233 "y" | "yes" | "" => ApprovalAnswer::Yes,
234 "n" | "no" => ApprovalAnswer::No,
235 "a" | "abort" | "q" | "quit" => ApprovalAnswer::Abort,
236 _ => ApprovalAnswer::Yes, }
238}