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) overriding_auth: Vec<String>,
24 pub(crate) argv_fallback: bool,
27 pub(crate) caller: String,
30 pub(crate) claude_program: PathBuf,
32 pub(crate) api_base: Option<String>,
34 pub(crate) hover_rest: bool,
36}
37
38impl Context {
39 pub fn new(home: PathBuf) -> Context {
43 Context {
44 pitboard_home: home.join(".pitboard"),
45 home,
46 claude_config_dir: None,
47 secure_storage_dir: None,
48 user: None,
49 custom_oauth: false,
50 argv_fallback: false,
51 overriding_auth: Vec::new(),
52 caller: "unknown".into(),
53 claude_program: PathBuf::from("claude"),
54 api_base: None,
55 hover_rest: false,
56 }
57 }
58
59 pub fn with_pitboard_home(mut self, dir: PathBuf) -> Context {
60 self.pitboard_home = dir;
61 self
62 }
63
64 pub fn with_claude_config_dir(mut self, dir: String) -> Context {
66 self.claude_config_dir = Some(dir).filter(|d| !d.is_empty());
67 self
68 }
69
70 pub fn with_secure_storage_dir(mut self, dir: String) -> Context {
73 self.secure_storage_dir = Some(dir);
74 self
75 }
76
77 pub fn with_argv_fallback(mut self, allowed: bool) -> Context {
80 self.argv_fallback = allowed;
81 self
82 }
83
84 pub fn custom_oauth(&self) -> bool {
86 self.custom_oauth
87 }
88
89 pub fn claude_program(&self) -> &std::path::Path {
91 &self.claude_program
92 }
93
94 pub fn argv_fallback(&self) -> bool {
96 self.argv_fallback
97 }
98
99 pub fn overriding_auth(&self) -> &[String] {
101 &self.overriding_auth
102 }
103
104 pub fn with_caller(mut self, caller: String) -> Context {
106 self.caller = caller;
107 self
108 }
109
110 pub fn with_user(mut self, user: String) -> Context {
111 self.user = Some(user);
112 self
113 }
114
115 pub fn with_claude_program(mut self, program: PathBuf) -> Context {
117 self.claude_program = program;
118 self
119 }
120
121 pub fn from_env() -> Context {
122 let var = |name: &str| std::env::var(name).ok();
123 let home = std::env::var_os("HOME")
124 .map(PathBuf::from)
125 .unwrap_or_default();
126 Context {
127 pitboard_home: std::env::var_os("PITBOARD_HOME")
128 .map(PathBuf::from)
129 .unwrap_or_else(|| home.join(".pitboard")),
130 home,
131 claude_config_dir: var("CLAUDE_CONFIG_DIR").filter(|v| !v.is_empty()),
132 secure_storage_dir: var("CLAUDE_SECURESTORAGE_CONFIG_DIR"),
133 user: var("USER"),
134 custom_oauth: var("CLAUDE_CODE_CUSTOM_OAUTH_URL").is_some_and(|v| !v.is_empty()),
135 argv_fallback: var("PITBOARD_ARGV_FALLBACK").is_some_and(|v| v == "1"),
136 overriding_auth: OVERRIDING_AUTH
137 .iter()
138 .filter(|name| var(name).is_some_and(|v| !v.is_empty()))
139 .map(|name| (*name).to_string())
140 .collect(),
141 caller: "cli".into(),
142 claude_program: PathBuf::from("claude"),
143 api_base: var("PITBOARD_API_BASE"),
144 hover_rest: var("CLAUDE_CODE_HOVER_REST").is_some_and(|v| v == "1" || v == "true"),
145 }
146 }
147}
148
149const OVERRIDING_AUTH: [&str; 3] = [
152 "ANTHROPIC_API_KEY",
153 "ANTHROPIC_AUTH_TOKEN",
154 "CLAUDE_CODE_OAUTH_TOKEN",
155];
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn an_explicit_context_reads_claude_codes_settings_the_way_the_environment_does() {
163 let ctx = Context::new(PathBuf::from("/home/x"))
164 .with_claude_config_dir(String::new())
165 .with_secure_storage_dir(String::new());
166 assert_eq!(ctx.pitboard_home, PathBuf::from("/home/x/.pitboard"));
167 assert_eq!(ctx.claude_config_dir, None, "empty means unset");
168 assert_eq!(
169 ctx.secure_storage_dir.as_deref(),
170 Some(""),
171 "empty is set, and pins the default slot"
172 );
173 assert_eq!(ctx.claude_program, PathBuf::from("claude"));
174 }
175}