mixtape_cli/repl/
approval.rs

1//! Tool approval handler for CLI
2//!
3//! Provides simple approval prompts for tool execution permissions.
4//! The v1 model offers:
5//! - Approve once (don't remember)
6//! - Trust this exact call (session only)
7//! - Trust the entire tool (session only)
8//! - Deny
9
10use mixtape_core::permission::{AuthorizationResponse, Grant, Scope};
11use std::io::{stdout, BufRead, Write};
12
13// =============================================================================
14// Core Types
15// =============================================================================
16
17/// All information needed to prompt for permission approval
18#[derive(Debug, Clone)]
19pub struct PermissionRequest {
20    /// Tool name
21    pub tool_name: String,
22    /// Tool use ID (for responding to the agent)
23    pub tool_use_id: String,
24    /// Hash of parameters (for exact-match grants)
25    pub params_hash: String,
26    /// Formatted display of the full tool call (if available)
27    pub formatted_display: Option<String>,
28}
29
30/// Trait for approval prompt implementations
31///
32/// Implement this to create custom approval UX.
33pub trait ApprovalPrompter: Send + Sync {
34    /// Prompt the user and return their choice
35    fn prompt(&self, request: &PermissionRequest) -> AuthorizationResponse;
36
37    /// Human-readable name for this prompter
38    fn name(&self) -> &'static str;
39}
40
41// =============================================================================
42// Default Prompter Implementation
43// =============================================================================
44
45/// Simple approval prompter with clear options
46///
47/// Displays:
48/// - y: approve once
49/// - e: trust this exact call
50/// - t: trust entire tool
51/// - n: deny
52pub struct SimplePrompter;
53
54impl ApprovalPrompter for SimplePrompter {
55    fn name(&self) -> &'static str {
56        "SimplePrompter"
57    }
58
59    fn prompt(&self, request: &PermissionRequest) -> AuthorizationResponse {
60        // Print the tool call header
61        print_tool_header(request);
62
63        // Print options
64        println!("\n\x1b[33mPermission required:\x1b[0m");
65        println!("  \x1b[1my\x1b[0m  approve once");
66        println!("  \x1b[1me\x1b[0m  trust this exact call (session)");
67        println!("  \x1b[1mt\x1b[0m  trust entire tool (session)");
68        println!("  \x1b[1mn\x1b[0m  deny");
69
70        loop {
71            print!("\nChoice: ");
72            let _ = stdout().flush();
73
74            let input = read_input();
75            let input = input.trim().to_lowercase();
76
77            match input.as_str() {
78                "y" | "yes" => {
79                    print_confirmation("Approved once");
80                    return AuthorizationResponse::Once;
81                }
82                "e" | "exact" => {
83                    let grant = Grant::exact(&request.tool_name, &request.params_hash)
84                        .with_scope(Scope::Session);
85                    print_confirmation("Trusted exact call for session");
86                    return AuthorizationResponse::Trust { grant };
87                }
88                "t" | "tool" | "trust" => {
89                    let grant = Grant::tool(&request.tool_name).with_scope(Scope::Session);
90                    print_confirmation("Trusted entire tool for session");
91                    return AuthorizationResponse::Trust { grant };
92                }
93                "n" | "no" | "deny" => {
94                    print_confirmation("Denied");
95                    return AuthorizationResponse::Deny { reason: None };
96                }
97                "" => continue,
98                _ => {
99                    println!("\x1b[31mInvalid choice. Use y/e/t/n\x1b[0m");
100                }
101            }
102        }
103    }
104}
105
106/// Default prompter type
107pub type DefaultPrompter = SimplePrompter;
108
109// =============================================================================
110// Helper Functions
111// =============================================================================
112
113/// Print the tool header with emoji
114pub fn print_tool_header(request: &PermissionRequest) {
115    println!("\n🛠️  \x1b[1m{}\x1b[0m", request.tool_name);
116
117    if let Some(ref display) = request.formatted_display {
118        for line in display.lines() {
119            println!("  {}", line);
120        }
121    }
122}
123
124/// Read a line of input
125pub fn read_input() -> String {
126    let stdin = std::io::stdin();
127    let mut line = String::new();
128    let _ = stdin.lock().read_line(&mut line);
129    line
130}
131
132/// Print a confirmation message
133pub fn print_confirmation(message: &str) {
134    println!("  \x1b[32m✓\x1b[0m {}", message);
135}
136
137/// Convenience function using the default prompter
138pub fn prompt_for_approval(request: &PermissionRequest) -> AuthorizationResponse {
139    SimplePrompter.prompt(request)
140}