Skip to main content

gemini_cli/auth/
sync.rs

1use std::path::{Path, PathBuf};
2
3use crate::auth;
4use crate::auth::output;
5
6pub fn run() -> i32 {
7    run_with_json(false)
8}
9
10pub fn run_with_json(output_json: bool) -> i32 {
11    let auth_file = match crate::paths::resolve_auth_file() {
12        Some(path) => path,
13        None => {
14            if output_json {
15                let _ = output::emit_result(
16                    "auth sync",
17                    output::obj(vec![
18                        ("auth_file", output::s("")),
19                        ("synced", output::n(0)),
20                        ("skipped", output::n(0)),
21                        ("failed", output::n(0)),
22                        ("updated_files", output::arr(Vec::new())),
23                    ]),
24                );
25            }
26            return 0;
27        }
28    };
29
30    if !auth_file.is_file() {
31        if output_json {
32            let _ = output::emit_result(
33                "auth sync",
34                output::obj(vec![
35                    ("auth_file", output::s(auth_file.display().to_string())),
36                    ("synced", output::n(0)),
37                    ("skipped", output::n(1)),
38                    ("failed", output::n(0)),
39                    ("updated_files", output::arr(Vec::new())),
40                ]),
41            );
42        }
43        return 0;
44    }
45
46    let auth_key = match auth::identity_key_from_auth_file(&auth_file) {
47        Ok(Some(key)) => key,
48        _ => {
49            if output_json {
50                let _ = output::emit_result(
51                    "auth sync",
52                    output::obj(vec![
53                        ("auth_file", output::s(auth_file.display().to_string())),
54                        ("synced", output::n(0)),
55                        ("skipped", output::n(1)),
56                        ("failed", output::n(0)),
57                        ("updated_files", output::arr(Vec::new())),
58                    ]),
59                );
60            }
61            return 0;
62        }
63    };
64
65    let auth_last_refresh = auth::last_refresh_from_auth_file(&auth_file).ok().flatten();
66    let auth_contents = match std::fs::read(&auth_file) {
67        Ok(contents) => contents,
68        Err(_) => {
69            if output_json {
70                let _ = output::emit_error(
71                    "auth sync",
72                    "auth-read-failed",
73                    format!("failed to read {}", auth_file.display()),
74                    Some(output::obj(vec![(
75                        "path",
76                        output::s(auth_file.display().to_string()),
77                    )])),
78                );
79            } else {
80                eprintln!("gemini: failed to read {}", auth_file.display());
81            }
82            return 1;
83        }
84    };
85
86    let mut synced = 0usize;
87    let mut skipped = 0usize;
88    let failed = 0usize;
89    let mut updated_files: Vec<String> = Vec::new();
90
91    if let Some(secret_dir) = crate::paths::resolve_secret_dir()
92        && let Ok(entries) = std::fs::read_dir(&secret_dir)
93    {
94        for entry in entries.flatten() {
95            let path = entry.path();
96            if path.extension().and_then(|s| s.to_str()) != Some("json") {
97                continue;
98            }
99
100            let candidate_key = match auth::identity_key_from_auth_file(&path) {
101                Ok(Some(key)) => key,
102                _ => {
103                    skipped += 1;
104                    continue;
105                }
106            };
107
108            if candidate_key != auth_key {
109                skipped += 1;
110                continue;
111            }
112
113            let secret_contents = match std::fs::read(&path) {
114                Ok(contents) => contents,
115                Err(_) => {
116                    if output_json {
117                        let _ = output::emit_error(
118                            "auth sync",
119                            "secret-read-failed",
120                            format!("failed to read {}", path.display()),
121                            Some(output::obj(vec![(
122                                "path",
123                                output::s(path.display().to_string()),
124                            )])),
125                        );
126                    } else {
127                        eprintln!("gemini: failed to read {}", path.display());
128                    }
129                    return 1;
130                }
131            };
132
133            if secret_contents == auth_contents {
134                skipped += 1;
135                continue;
136            }
137
138            if auth::write_atomic(&path, &auth_contents, auth::SECRET_FILE_MODE).is_err() {
139                if output_json {
140                    let _ = output::emit_error(
141                        "auth sync",
142                        "sync-write-failed",
143                        format!("failed to write {}", path.display()),
144                        Some(output::obj(vec![(
145                            "path",
146                            output::s(path.display().to_string()),
147                        )])),
148                    );
149                } else {
150                    eprintln!("gemini: failed to write {}", path.display());
151                }
152                return 1;
153            }
154
155            if let Some(timestamp_path) = secret_timestamp_path(&path) {
156                let _ = auth::write_timestamp(&timestamp_path, auth_last_refresh.as_deref());
157            }
158            synced += 1;
159            updated_files.push(path.display().to_string());
160        }
161    }
162
163    if let Some(auth_timestamp) = secret_timestamp_path(&auth_file) {
164        let _ = auth::write_timestamp(&auth_timestamp, auth_last_refresh.as_deref());
165    }
166
167    if output_json {
168        let _ = output::emit_result(
169            "auth sync",
170            output::obj(vec![
171                ("auth_file", output::s(auth_file.display().to_string())),
172                ("synced", output::n(synced as i64)),
173                ("skipped", output::n(skipped as i64)),
174                ("failed", output::n(failed as i64)),
175                (
176                    "updated_files",
177                    output::arr(updated_files.into_iter().map(output::s).collect()),
178                ),
179            ]),
180        );
181    }
182
183    0
184}
185
186fn secret_timestamp_path(target_file: &Path) -> Option<PathBuf> {
187    let cache_dir = crate::paths::resolve_secret_cache_dir()?;
188    let name = target_file
189        .file_name()
190        .and_then(|name| name.to_str())
191        .unwrap_or("auth.json");
192    Some(cache_dir.join(format!("{name}.timestamp")))
193}