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}