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