1pub mod copilot;
2mod login;
3pub mod oauth;
4mod ui;
5
6pub use login::login_flow;
7
8use anyhow::{Context, Result};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::path::PathBuf;
12
13#[derive(Debug, Clone, Serialize, Deserialize, Default)]
14pub struct Credentials {
15 pub providers: HashMap<String, ProviderCredential>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(tag = "type", rename_all = "snake_case")]
20pub enum ProviderCredential {
21 ApiKey {
22 key: String,
23 },
24 OAuth {
25 access_token: String,
26 refresh_token: Option<String>,
27 expires_at: Option<i64>,
28 api_key: Option<String>,
29 },
30}
31
32impl ProviderCredential {
33 pub fn api_key(&self) -> Option<&str> {
34 match self {
35 ProviderCredential::ApiKey { key } => Some(key.as_str()),
36 ProviderCredential::OAuth {
37 api_key: Some(k), ..
38 } => Some(k.as_str()),
39 ProviderCredential::OAuth { access_token, .. } => Some(access_token.as_str()),
40 }
41 }
42}
43
44impl Credentials {
45 fn path() -> PathBuf {
46 crate::config::Config::config_dir().join("credentials.json")
47 }
48
49 pub fn load() -> Result<Self> {
50 let path = Self::path();
51 if path.exists() {
52 let content = std::fs::read_to_string(&path).context("reading credentials file")?;
53 serde_json::from_str(&content).context("parsing credentials file")
54 } else {
55 Ok(Self::default())
56 }
57 }
58
59 pub fn save(&self) -> Result<()> {
60 let path = Self::path();
61 if let Some(parent) = path.parent() {
62 std::fs::create_dir_all(parent)?;
63 }
64 std::fs::write(&path, serde_json::to_string_pretty(self)?)
65 .context("writing credentials file")
66 }
67
68 pub fn get(&self, provider: &str) -> Option<&ProviderCredential> {
69 self.providers.get(provider)
70 }
71
72 pub fn set(&mut self, provider: &str, cred: ProviderCredential) {
73 self.providers.insert(provider.to_string(), cred);
74 }
75}