1use serde::{Deserialize, Serialize};
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, Serialize, Deserialize)]
5pub struct TracevaultConfig {
6 pub agent: String,
7 pub server_url: Option<String>,
8 pub api_key: Option<String>,
9 pub org_slug: Option<String>,
10}
11
12impl Default for TracevaultConfig {
13 fn default() -> Self {
14 Self {
15 agent: "claude-code".to_string(),
16 server_url: None,
17 api_key: None,
18 org_slug: None,
19 }
20 }
21}
22
23impl TracevaultConfig {
24 pub fn config_dir(project_root: &Path) -> PathBuf {
25 project_root.join(".tracevault")
26 }
27
28 pub fn config_path(project_root: &Path) -> PathBuf {
29 Self::config_dir(project_root).join("config.toml")
30 }
31
32 pub fn to_toml(&self) -> String {
33 let mut out = format!("# TraceVault configuration\nagent = \"{}\"\n", self.agent);
34 if let Some(url) = &self.server_url {
35 out.push_str(&format!("server_url = \"{url}\"\n"));
36 }
37 if let Some(slug) = &self.org_slug {
38 out.push_str(&format!("org_slug = \"{slug}\"\n"));
39 }
40 out
41 }
42
43 pub fn load(project_root: &Path) -> Option<Self> {
46 let path = Self::config_path(project_root);
47 let content = std::fs::read_to_string(path).ok()?;
48
49 let parse_field = |key: &str| -> Option<String> {
50 content
51 .lines()
52 .find(|l| l.starts_with(key))
53 .and_then(|l| l.split('=').nth(1))
54 .map(|s| s.trim().trim_matches('"').to_string())
55 };
56
57 Some(Self {
58 agent: parse_field("agent").unwrap_or_else(|| "claude-code".to_string()),
59 server_url: parse_field("server_url"),
60 api_key: parse_field("api_key"),
61 org_slug: parse_field("org_slug"),
62 })
63 }
64}