Rust · 2358 bytes Raw Blame History
1 use std::path::PathBuf;
2
3 use crate::core::error::{CoreError, CoreResult};
4
5 /// `~/.claude/`
6 pub fn claude_home() -> CoreResult<PathBuf> {
7 dirs::home_dir()
8 .map(|h| h.join(".claude"))
9 .ok_or(CoreError::HomeNotFound)
10 }
11
12 /// `~/.claude/projects/`
13 pub fn projects_dir() -> CoreResult<PathBuf> {
14 Ok(claude_home()?.join("projects"))
15 }
16
17 /// Best-effort decode of an encoded-cwd directory name into a path.
18 ///
19 /// Claude Code encodes cwds by replacing every `/` with `-`. This is
20 /// lossy — a directory named `my-cool-project` is indistinguishable from
21 /// three nested dirs `my/cool/project`, and hidden-dir segments (e.g.
22 /// `/.claude-mem/`) surface as `--` in the encoding.
23 ///
24 /// The decoded path returned here is **a best guess only** and callers
25 /// MUST verify it against the `cwd` field inside the first event of any
26 /// session living in the encoded directory. Authoritative answer lives
27 /// in the JSONL payload, not the filename.
28 pub fn decode_cwd(encoded: &str) -> PathBuf {
29 PathBuf::from(encoded.replace('-', "/"))
30 }
31
32 /// Fallback display name when no event provides a cwd — takes the last
33 /// path segment of the best-guess decode.
34 pub fn display_name_from_encoded(encoded: &str) -> String {
35 let decoded = decode_cwd(encoded);
36 decoded
37 .file_name()
38 .and_then(|n| n.to_str())
39 .map(|s| s.to_owned())
40 .unwrap_or_else(|| encoded.to_owned())
41 }
42
43 #[cfg(test)]
44 mod tests {
45 use super::*;
46 use pretty_assertions::assert_eq;
47
48 #[test]
49 fn decode_basic_path() {
50 assert_eq!(
51 decode_cwd("-Users-alice-Documents-claudex"),
52 PathBuf::from("/Users/alice/Documents/claudex"),
53 );
54 }
55
56 #[test]
57 fn decode_preserves_collisions_for_callers_to_resolve() {
58 // Either interpretation is valid without the in-event cwd.
59 // We just do the mechanical replacement.
60 assert_eq!(
61 decode_cwd("-Users-alice-my-cool-project"),
62 PathBuf::from("/Users/alice/my/cool/project"),
63 );
64 }
65
66 #[test]
67 fn display_name_for_simple_path() {
68 assert_eq!(
69 display_name_from_encoded("-Users-alice-Documents-claudex"),
70 "claudex",
71 );
72 }
73
74 #[test]
75 fn display_name_falls_back_to_raw_on_empty() {
76 assert_eq!(display_name_from_encoded(""), "");
77 }
78 }
79