1use anyhow::{bail, Result};
7
8use crate::inference::InferenceBackend;
9
10#[derive(Debug, Clone)]
11pub struct AuthState {
12 pub api_key: Option<String>,
13 pub backend: InferenceBackend,
14}
15
16pub struct AuthGate {
17 state: AuthState,
18}
19
20impl Default for AuthGate {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl AuthGate {
27 pub fn new() -> Self {
28 Self {
29 state: AuthState {
30 api_key: None,
31 backend: InferenceBackend::Cli,
32 },
33 }
34 }
35
36 pub fn authenticate(&mut self) -> Result<&AuthState> {
37 let api_key = std::env::var("ANTHROPIC_API_KEY").ok();
38 let force_api = std::env::var("AGENT_BACKEND").as_deref() == Ok("api");
39
40 if force_api {
41 if api_key.is_none() {
42 bail!(
43 "API backend requested but ANTHROPIC_API_KEY not set.\n\
44 Set it with: export ANTHROPIC_API_KEY=<your-key>\n\
45 Or remove AGENT_BACKEND=api to use CLI mode."
46 );
47 }
48 self.state = AuthState {
49 api_key,
50 backend: InferenceBackend::Api,
51 };
52 } else {
53 let claude_exists = std::process::Command::new("which")
55 .arg("claude")
56 .output()
57 .map(|o| o.status.success())
58 .unwrap_or(false);
59
60 if !claude_exists {
61 bail!(
62 "claude CLI not found in PATH.\n\
63 Install Claude Code: npm install -g @anthropic-ai/claude-code\n\
64 Or set AGENT_BACKEND=api with ANTHROPIC_API_KEY for direct API mode."
65 );
66 }
67
68 self.state = AuthState {
69 api_key,
70 backend: InferenceBackend::Cli,
71 };
72 }
73
74 Ok(&self.state)
75 }
76
77 pub fn api_key(&self) -> Option<&str> {
78 self.state.api_key.as_deref()
79 }
80
81 pub fn backend(&self) -> InferenceBackend {
82 self.state.backend
83 }
84
85}