diff --git a/src/inject.rs b/src/inject.rs index 9a7281f..b132c61 100644 --- a/src/inject.rs +++ b/src/inject.rs @@ -1,7 +1,7 @@ use crate::report::format_tokens; use crate::types::Report; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; struct Rule { category: &'static str, @@ -107,12 +107,15 @@ pub fn generate_rules(report: &Report) -> String { lines.join("\n") } -pub fn inject_rules(report: &Report) { +pub fn inject_rules(report: &Report, claude_base: Option<&Path>) { let rules_content = generate_rules(report); - let claude_dir = dirs::home_dir() - .unwrap_or_else(|| PathBuf::from(".")) - .join(".claude"); + let claude_dir = match claude_base { + Some(base) => base.to_path_buf(), + None => dirs::home_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join(".claude"), + }; let rules_path = claude_dir.join("ccwasted-rules.md"); let claude_md_path = claude_dir.join("CLAUDE.md"); diff --git a/src/main.rs b/src/main.rs index d0691d7..6b3bd50 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod types; use chrono::Local; use clap::Parser; +use std::path::PathBuf; use types::{Report, SessionInfo, SessionReport}; #[derive(Parser)] @@ -44,13 +45,18 @@ struct Cli { /// Filter by project directory (matches against JSONL project paths) #[arg(long)] project_dir: Option, + + /// Path to Claude instance directory (default: ~/.claude). Use to target ~/.claude-work, ~/.claude-personal, etc. + #[arg(long, short = 'C', value_hint = clap::ValueHint::DirPath)] + claude_dir: Option, } fn main() { let cli = Cli::parse(); let today = Local::now().date_naive(); - let found = scanner::find_sessions(cli.days, cli.project_dir.as_deref()); + let claude_dir = cli.claude_dir.as_deref(); + let found = scanner::find_sessions(cli.days, cli.project_dir.as_deref(), claude_dir); if found.is_empty() { if cli.json { let empty = Report { @@ -149,7 +155,7 @@ fn main() { let rules = inject::generate_rules(&report); println!("{}", rules); } else if cli.inject { - inject::inject_rules(&report); + inject::inject_rules(&report, claude_dir); } else if cli.json { json_report::print_json(&report); } else { diff --git a/src/scanner.rs b/src/scanner.rs index 1904c5c..69ce99e 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -11,12 +11,16 @@ pub struct FoundSession { pub project_name: String, } -pub fn find_sessions(days: u32, project_dir: Option<&str>) -> Vec { +pub fn find_sessions(days: u32, project_dir: Option<&str>, claude_base: Option<&Path>) -> Vec { let today = Local::now().date_naive(); let since = today - chrono::Duration::days(days as i64 - 1); - let claude_dir = dirs::home_dir() - .unwrap_or_else(|| PathBuf::from(".")) - .join(".claude/projects"); + let claude_dir = match claude_base { + Some(base) => base.join("projects"), + None => dirs::home_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join(".claude") + .join("projects"), + }; if !claude_dir.exists() { return vec![]; } @@ -189,4 +193,25 @@ mod tests { assert!(!files.is_empty()); assert!(files.iter().any(|f| f.path.ends_with("minimal.jsonl"))); } + + #[test] + fn test_find_sessions_with_custom_claude_base() { + let tmp = std::env::temp_dir().join("ccwasted_test_claude_base"); + let project_dir = tmp.join("projects").join("-test-project"); + std::fs::create_dir_all(&project_dir).unwrap(); + + // Copy a fixture jsonl into the fake project dir with a recent mtime + let src = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/minimal.jsonl"); + let dst = project_dir.join("session-abc123.jsonl"); + std::fs::copy(&src, &dst).unwrap(); + + let sessions = find_sessions(30, None, Some(&tmp)); + std::fs::remove_dir_all(&tmp).unwrap(); + + assert!(!sessions.is_empty(), "should find sessions in custom claude_base"); + assert!( + sessions[0].main_jsonl.starts_with(&project_dir), + "session path should be under the custom base, not ~/.claude" + ); + } }