1use std::path::PathBuf;
6
7#[derive(Clone, Debug)]
8pub struct Context {
9 pub(crate) home: PathBuf,
10 pub(crate) pitboard_home: PathBuf,
11 pub(crate) claude_config_dir: Option<String>,
13 pub(crate) secure_storage_dir: Option<String>,
16 pub(crate) user: Option<String>,
18 pub(crate) custom_oauth: bool,
21 pub(crate) claude_program: PathBuf,
23 pub(crate) api_base: Option<String>,
25 pub(crate) hover_rest: bool,
27}
28
29impl Context {
30 pub fn new(home: PathBuf) -> Context {
34 Context {
35 pitboard_home: home.join(".pitboard"),
36 home,
37 claude_config_dir: None,
38 secure_storage_dir: None,
39 user: None,
40 custom_oauth: false,
41 claude_program: PathBuf::from("claude"),
42 api_base: None,
43 hover_rest: false,
44 }
45 }
46
47 pub fn with_pitboard_home(mut self, dir: PathBuf) -> Context {
48 self.pitboard_home = dir;
49 self
50 }
51
52 pub fn with_claude_config_dir(mut self, dir: String) -> Context {
54 self.claude_config_dir = Some(dir).filter(|d| !d.is_empty());
55 self
56 }
57
58 pub fn with_secure_storage_dir(mut self, dir: String) -> Context {
61 self.secure_storage_dir = Some(dir);
62 self
63 }
64
65 pub fn with_user(mut self, user: String) -> Context {
67 self.user = Some(user);
68 self
69 }
70
71 pub fn with_claude_program(mut self, program: PathBuf) -> Context {
73 self.claude_program = program;
74 self
75 }
76
77 pub fn from_env() -> Context {
78 let var = |name: &str| std::env::var(name).ok();
79 let home = std::env::var_os("HOME")
80 .map(PathBuf::from)
81 .unwrap_or_default();
82 Context {
83 pitboard_home: std::env::var_os("PITBOARD_HOME")
84 .map(PathBuf::from)
85 .unwrap_or_else(|| home.join(".pitboard")),
86 home,
87 claude_config_dir: var("CLAUDE_CONFIG_DIR").filter(|v| !v.is_empty()),
88 secure_storage_dir: var("CLAUDE_SECURESTORAGE_CONFIG_DIR"),
89 user: var("USER"),
90 custom_oauth: var("CLAUDE_CODE_CUSTOM_OAUTH_URL").is_some_and(|v| !v.is_empty()),
91 claude_program: PathBuf::from("claude"),
92 api_base: var("PITBOARD_API_BASE"),
93 hover_rest: var("CLAUDE_CODE_HOVER_REST").is_some_and(|v| v == "1" || v == "true"),
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn an_explicit_context_reads_claude_codes_settings_the_way_the_environment_does() {
104 let ctx = Context::new(PathBuf::from("/home/x"))
105 .with_claude_config_dir(String::new())
106 .with_secure_storage_dir(String::new());
107 assert_eq!(ctx.pitboard_home, PathBuf::from("/home/x/.pitboard"));
108 assert_eq!(ctx.claude_config_dir, None, "empty means unset");
109 assert_eq!(
110 ctx.secure_storage_dir.as_deref(),
111 Some(""),
112 "empty is set, and pins the default slot"
113 );
114 assert_eq!(ctx.claude_program, PathBuf::from("claude"));
115 }
116}