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
84impl fmt::Display for ApproveMode {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 ApproveMode::Auto => write!(f, "auto"),
88 ApproveMode::Ask => write!(f, "ask"),
89 ApproveMode::Strict => write!(f, "strict"),
90 }
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum ApprovalAnswer {
101 Yes,
103 No,
105 Abort,
107}
108
109#[derive(Debug, Clone)]
115pub struct ApprovalRequest {
116 pub tool_name: String,
117 pub risk_level: RiskLevel,
118 pub summary: String,
119}
120
121impl ApprovalRequest {
122 pub fn new(tool_name: &str, risk: RiskLevel, params: &Value) -> Self {
124 Self {
125 tool_name: tool_name.to_string(),
126 risk_level: risk,
127 summary: build_summary(tool_name, params),
128 }
129 }
130}
131
132fn build_summary(tool_name: &str, params: &Value) -> String {
134 match tool_name {
135 "write" => summary_write(params),
136 "edit" => summary_edit(params),
137 "multi_edit" => summary_multi_edit(params),
138 "bash" => summary_bash(params),
139 "todo_write" => "ๆดๆฐไปปๅกๆธ
ๅ".to_string(),
140 _ => format!("ๆง่กๅทฅๅ
ท: {}", tool_name),
141 }
142}
143
144fn summary_write(params: &Value) -> String {
145 let path = params["path"].as_str().unwrap_or("<unknown>");
146 format!("ๅๅ
ฅๆไปถ: {}", path)
147}
148
149fn summary_edit(params: &Value) -> String {
150 let path = params["path"].as_str().unwrap_or("<unknown>");
151 format!("็ผ่พๆไปถ: {}", path)
152}
153
154fn summary_multi_edit(params: &Value) -> String {
155 let path = params["path"].as_str().unwrap_or("<unknown>");
156 let count = params["edits"].as_array().map(|a| a.len()).unwrap_or(0);
157 format!("ๆน้็ผ่พๆไปถ: {} ({} ๅคไฟฎๆน)", path, count)
158}
159
160fn summary_bash(params: &Value) -> String {
161 let cmd = params["command"].as_str().unwrap_or("<unknown>");
162 let display_cmd = if cmd.len() > 120 {
163 format!("{}...", &cmd[..120])
164 } else {
165 cmd.to_string()
166 };
167 format!("ๆง่กๅฝไปค: {}", display_cmd)
168}
169
170pub fn needs_approval(mode: ApproveMode, risk: RiskLevel) -> bool {
176 match mode {
177 ApproveMode::Auto => false,
178 ApproveMode::Ask => risk >= RiskLevel::Mutating,
179 ApproveMode::Strict => true,
180 }
181}
182
183pub fn build_approval_request(tool_name: &str, risk: RiskLevel, params: &Value) -> ApprovalRequest {
185 ApprovalRequest::new(tool_name, risk, params)
186}
187
188pub fn prompt_approval(request: &ApprovalRequest) -> ApprovalAnswer {
191 println!();
192 println!("โโ ็กฎ่ฎค่ฏทๆฑ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
193 println!("โ {} {}", request.risk_level.icon(), request.summary);
194 println!("โ ้ฃ้ฉ็ญ็บง: {}", request.risk_level);
195 println!("โ");
196 println!("โ [y] ๆง่ก [n] ่ทณ่ฟ [a] ไธญๆญขๆฌ่ฝฎ");
197 println!("โโโโ๏ฟฝ๏ฟฝ๏ฟฝโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
198 print!("> ");
199 let _ = io::stdout().flush();
200
201 let answer = read_approval_answer();
202 println!();
203 answer
204}
205
206fn read_approval_answer() -> ApprovalAnswer {
208 let stdin = io::stdin();
209 let mut line = String::new();
210 if stdin.lock().read_line(&mut line).is_err() {
211 return ApprovalAnswer::No;
212 }
213 match line.trim().to_lowercase().as_str() {
214 "y" | "yes" | "" => ApprovalAnswer::Yes,
215 "n" | "no" => ApprovalAnswer::No,
216 "a" | "abort" | "q" | "quit" => ApprovalAnswer::Abort,
217 _ => ApprovalAnswer::Yes, }
219}