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        // Tool name and params already displayed by ToolRequested event
61        println!("│");
62        println!(
63            "│  \x1b[33mApprove?\x1b[0m  \x1b[2m(y)es  (n)o  (t)rust tool  (e)xact match\x1b[0m"
64        );
65
66        loop {
67            print!("│  > ");
68            let _ = stdout().flush();
69
70            let input = read_input();
71            let input = input.trim().to_lowercase();
72
73            match input.as_str() {
74                "y" | "yes" => {
75                    return AuthorizationResponse::Once;
76                }
77                "e" | "exact" => {
78                    let grant = Grant::exact(&_request.tool_name, &_request.params_hash)
79                        .with_scope(Scope::Session);
80                    return AuthorizationResponse::Trust { grant };
81                }
82                "t" | "tool" | "trust" => {
83                    let grant = Grant::tool(&_request.tool_name).with_scope(Scope::Session);
84                    return AuthorizationResponse::Trust { grant };
85                }
86                "n" | "no" | "deny" => {
87                    return AuthorizationResponse::Deny { reason: None };
88                }
89                "" => continue,
90                _ => {
91                    println!("│  \x1b[31mUse y/n/t/e\x1b[0m");
92                }
93            }
94        }
95    }
96}
97
98/// Default prompter type
99pub type DefaultPrompter = SimplePrompter;
100
101// =============================================================================
102// Helper Functions
103// =============================================================================
104
105/// Read a line of input
106pub fn read_input() -> String {
107    let stdin = std::io::stdin();
108    let mut line = String::new();
109    let _ = stdin.lock().read_line(&mut line);
110    line
111}
112
113/// Print a confirmation message
114pub fn print_confirmation(message: &str) {
115    println!("  \x1b[32m✓\x1b[0m {}", message);
116}
117
118/// Convenience function using the default prompter
119pub fn prompt_for_approval(request: &PermissionRequest) -> AuthorizationResponse {
120    SimplePrompter.prompt(request)
121}