token_count/api/consent.rs
1//! Interactive consent prompt for API calls
2//!
3//! This module provides a reusable consent mechanism for prompting users
4//! before sending data to external APIs. It includes TTY detection to
5//! prevent hanging in non-interactive environments.
6
7use crate::error::TokenError;
8use std::io::{self, IsTerminal, Write};
9
10/// Configuration for API consent prompt
11pub struct ConsentPrompt {
12 /// Provider name (e.g., "Anthropic", "OpenAI")
13 pub provider: &'static str,
14
15 /// API endpoint URL (for transparency)
16 pub api_endpoint: &'static str,
17}
18
19impl ConsentPrompt {
20 /// Ask user for consent (interactive mode only)
21 ///
22 /// Returns `Ok(true)` if user consents, `Ok(false)` if user declines.
23 /// Returns `Err` if running in non-interactive mode (stdin not a TTY).
24 pub fn ask(&self) -> Result<bool, TokenError> {
25 // Check if stdin is a TTY (terminal)
26 if !io::stdin().is_terminal() {
27 return Err(TokenError::NonInteractiveWithoutYes {
28 model: "claude".to_string(), // Default model for error message
29 });
30 }
31
32 // Display prompt on stderr (don't pollute stdout)
33 eprintln!();
34 eprintln!(
35 "This will send your input to {}'s API for accurate token counting.",
36 self.provider
37 );
38 eprintln!("Your input will be transmitted over HTTPS to: {}", self.api_endpoint);
39 eprintln!();
40 eprint!("Proceed with API call? (y/N): ");
41
42 // Flush stderr to ensure prompt is visible
43 io::stderr().flush().map_err(|e| {
44 TokenError::Io(io::Error::other(format!("Failed to flush stderr: {}", e)))
45 })?;
46
47 // Read user response from stdin
48 let mut response = String::new();
49 io::stdin().read_line(&mut response).map_err(TokenError::Io)?;
50
51 // Accept: y, Y, yes, YES (case-insensitive)
52 // Reject: n, N, no, NO, empty, anything else
53 let normalized = response.trim().to_lowercase();
54 Ok(normalized == "y" || normalized == "yes")
55 }
56}