1use std::io::ErrorKind;
2use std::path::Path;
3
4use crate::auth;
5use crate::auth::output;
6
7pub fn run() -> i32 {
8 run_with_json(false)
9}
10
11pub fn run_with_json(output_json: bool) -> i32 {
12 let auth_file = match crate::paths::resolve_auth_file() {
13 Some(path) => path,
14 None => {
15 if output_json {
16 let _ = output::emit_error(
17 "auth current",
18 "auth-file-not-configured",
19 "GEMINI_AUTH_FILE is not configured",
20 None,
21 );
22 }
23 return 1;
24 }
25 };
26
27 if !auth_file.is_file() {
28 if output_json {
29 let _ = output::emit_error(
30 "auth current",
31 "auth-file-not-found",
32 format!("{} not found", auth_file.display()),
33 Some(output::obj(vec![(
34 "auth_file",
35 output::s(auth_file.display().to_string()),
36 )])),
37 );
38 } else {
39 eprintln!("gemini: {} not found", auth_file.display());
40 }
41 return 1;
42 }
43
44 let auth_key = auth::identity_key_from_auth_file(&auth_file).ok().flatten();
45 let auth_contents = match std::fs::read(&auth_file) {
46 Ok(bytes) => bytes,
47 Err(_) => {
48 eprintln!("gemini: failed to read {}", auth_file.display());
49 return 1;
50 }
51 };
52
53 let secret_dir = match crate::paths::resolve_secret_dir() {
54 Some(path) => path,
55 None => {
56 emit_secret_dir_error(
57 output_json,
58 "secret-dir-not-configured",
59 "GEMINI_SECRET_DIR is not configured".to_string(),
60 None,
61 );
62 return 1;
63 }
64 };
65
66 let entries = match std::fs::read_dir(&secret_dir) {
67 Ok(entries) => entries,
68 Err(err) if err.kind() == ErrorKind::NotFound => {
69 emit_secret_dir_error(
70 output_json,
71 "secret-dir-not-found",
72 format!("{} not found", secret_dir.display()),
73 Some(output::obj(vec![(
74 "secret_dir",
75 output::s(secret_dir.display().to_string()),
76 )])),
77 );
78 return 1;
79 }
80 Err(err) => {
81 emit_secret_dir_error(
82 output_json,
83 "secret-dir-read-failed",
84 format!("failed to read {}: {err}", secret_dir.display()),
85 Some(output::obj(vec![
86 ("secret_dir", output::s(secret_dir.display().to_string())),
87 ("error", output::s(err.to_string())),
88 ])),
89 );
90 return 1;
91 }
92 };
93
94 let mut matched: Option<(String, MatchMode)> = None;
95
96 for entry in entries.flatten() {
97 let path = entry.path();
98 if path.extension().and_then(|s| s.to_str()) != Some("json") {
99 continue;
100 }
101
102 if let Some(key) = auth_key.as_deref()
103 && let Ok(Some(candidate_key)) = auth::identity_key_from_auth_file(&path)
104 && candidate_key == key
105 {
106 let candidate_contents = match std::fs::read(&path) {
107 Ok(bytes) => bytes,
108 Err(_) => {
109 if output_json {
110 let _ = output::emit_error(
111 "auth current",
112 "secret-read-failed",
113 format!("failed to read {}", path.display()),
114 Some(output::obj(vec![(
115 "path",
116 output::s(path.display().to_string()),
117 )])),
118 );
119 } else {
120 eprintln!("gemini: failed to read {}", path.display());
121 }
122 return 1;
123 }
124 };
125
126 let mode = if candidate_contents == auth_contents {
127 MatchMode::Exact
128 } else {
129 MatchMode::Identity
130 };
131 matched = Some((file_name(&path), mode));
132 break;
133 }
134
135 let candidate_contents = match std::fs::read(&path) {
136 Ok(bytes) => bytes,
137 Err(_) => {
138 if output_json {
139 let _ = output::emit_error(
140 "auth current",
141 "secret-read-failed",
142 format!("failed to read {}", path.display()),
143 Some(output::obj(vec![(
144 "path",
145 output::s(path.display().to_string()),
146 )])),
147 );
148 } else {
149 eprintln!("gemini: failed to read {}", path.display());
150 }
151 return 1;
152 }
153 };
154
155 if candidate_contents == auth_contents {
156 matched = Some((file_name(&path), MatchMode::Exact));
157 break;
158 }
159 }
160
161 if let Some((secret_name, mode)) = matched {
162 if output_json {
163 let match_mode = match mode {
164 MatchMode::Exact => "exact",
165 MatchMode::Identity => "identity",
166 };
167 let _ = output::emit_result(
168 "auth current",
169 output::obj(vec![
170 ("auth_file", output::s(auth_file.display().to_string())),
171 ("matched", output::b(true)),
172 ("matched_secret", output::s(secret_name)),
173 ("match_mode", output::s(match_mode)),
174 ]),
175 );
176 } else {
177 match mode {
178 MatchMode::Exact => {
179 println!("gemini: {} matches {}", auth_file.display(), secret_name);
180 }
181 MatchMode::Identity => {
182 println!(
183 "gemini: {} matches {} (identity; secret differs)",
184 auth_file.display(),
185 secret_name
186 );
187 }
188 }
189 }
190 return 0;
191 }
192
193 if output_json {
194 let _ = output::emit_error(
195 "auth current",
196 "secret-not-matched",
197 format!("{} does not match any known secret", auth_file.display()),
198 Some(output::obj(vec![
199 ("auth_file", output::s(auth_file.display().to_string())),
200 ("matched", output::b(false)),
201 ])),
202 );
203 } else {
204 println!(
205 "gemini: {} does not match any known secret",
206 auth_file.display()
207 );
208 }
209 2
210}
211
212#[derive(Copy, Clone)]
213enum MatchMode {
214 Exact,
215 Identity,
216}
217
218fn file_name(path: &Path) -> String {
219 path.file_name()
220 .and_then(|name| name.to_str())
221 .unwrap_or_default()
222 .to_string()
223}
224
225fn emit_secret_dir_error(
226 output_json: bool,
227 code: &str,
228 message: String,
229 details: Option<output::JsonValue>,
230) {
231 if output_json {
232 let _ = output::emit_error("auth current", code, message, details);
233 } else {
234 eprintln!("gemini: {message}");
235 }
236}