diff --git a/src/auth.rs b/src/auth.rs index 51291a88..935fea4c 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -146,6 +146,10 @@ pub fn resolve_name(agent: &str, name: &str) -> String { } pub fn activate(agent: &str, profile: &str, json: bool) { + activate_with(agent, profile, false, json) +} + +pub fn activate_with(agent: &str, profile: &str, reload_daemon: bool, json: bool) { let profile = resolve_name(agent, profile); if let Err(e) = validate_name(agent, "agent").and_then(|_| validate_name(&profile, "profile")) { fail(&e, "", json); @@ -211,6 +215,19 @@ pub fn activate(agent: &str, profile: &str, json: bool) { } else { println!("activated {agent}/{profile} — restored {restored} file(s)"); } + + // Daemon check + if crate::auth_runner::daemon_running(agent) { + if reload_daemon { + if let Err(e) = crate::auth_runner::reload_daemon(agent) { + eprintln!("warning: failed to reload daemon: {e}"); + } else if !json { + println!("daemon restarted — auth changes now active"); + } + } else if !json { + eprintln!("warning: {agent} daemon is running — auth changes won't take effect until restart. Use --reload-daemon to auto-restart."); + } + } } pub fn status(agent: Option<&str>, json: bool) { @@ -722,6 +739,10 @@ fn isolates_dir() -> PathBuf { } pub fn isolate_add(agent: &str, profile: &str, json: bool) { + isolate_add_with(agent, profile, false, json) +} + +pub fn isolate_add_with(agent: &str, profile: &str, shallow: bool, json: bool) { let dir = isolates_dir().join(agent).join(profile); if dir.exists() { if json { @@ -733,17 +754,32 @@ pub fn isolate_add(agent: &str, profile: &str, json: bool) { } fs::create_dir_all(&dir).expect("create isolate dir"); - // Symlink shared host files - for host_file in &[".ssh", ".gitconfig", ".git-credentials"] { - let src = home().join(host_file); - if src.exists() { - symlink_or_copy(&src, &dir.join(host_file)); + if shallow { + // Symlink all host dirs, only auth files are real + let host_home = home(); + for entry in &[".cache", ".config", ".local", "Documents", "Downloads"] { + let src = host_home.join(entry); + if src.exists() { + symlink_or_copy(&src, &dir.join(entry)); + } + } + } else { + // Symlink shared host files only + for host_file in &[".ssh", ".gitconfig", ".git-credentials"] { + let src = home().join(host_file); + if src.exists() { + symlink_or_copy(&src, &dir.join(host_file)); + } } } // Copy auth files from vault profile activate_into(agent, profile, &dir); + // Store metadata + let meta = serde_json::json!({"mode": if shallow { "shallow" } else { "deep" }, "agent": agent, "profile": profile}); + fs::write(dir.join("isolate.json"), serde_json::to_string_pretty(&meta).unwrap() + "\n").ok(); + if json { println!("{}", serde_json::json!({"agent": agent, "profile": profile, "isolate_dir": dir.to_string_lossy()})); } else { @@ -776,10 +812,12 @@ pub fn isolate_ls(agent: Option<&str>, json: bool) { for entry in entries.flatten() { if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) { let p = entry.file_name().to_string_lossy().to_string(); + let mode = read_isolate_mode(&agent_dir.join(&p)); if json { - results.push(serde_json::json!({"agent": a, "profile": p})); + results.push(serde_json::json!({"agent": a, "profile": p, "mode": mode})); } else { - println!("{a}/{p}"); + let mode_str = mode.map_or(String::new(), |m| format!(" ({m})")); + println!("{a}/{p}{mode_str}"); } } } @@ -907,6 +945,16 @@ pub fn auth_login(agent: &str, profile: &str, args: &[String], json: bool) { } +fn read_isolate_mode(dir: &std::path::Path) -> Option { + let meta_path = dir.join("isolate.json"); + if let Ok(data) = fs::read_to_string(&meta_path) { + if let Ok(meta) = serde_json::from_str::(&data) { + return meta["mode"].as_str().map(|s| s.to_string()); + } + } + None +} + fn activate_into(agent: &str, profile: &str, target_dir: &std::path::Path) { let cat = match catalog_for(agent) { Some(c) => c, None => { return; } }; let vault = profile_dir(agent, profile); diff --git a/src/main.rs b/src/main.rs index 8c97d44c..48ebd3ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -207,6 +207,9 @@ enum AuthAction { profile: String, #[arg(long)] json: bool, + /// Restart agent daemon after auth restore (picks up new auth). + #[arg(long)] + reload_daemon: bool, }, /// Show active profile via content-hash detection. Status { @@ -327,6 +330,9 @@ enum IsolateAction { profile: String, #[arg(long)] json: bool, + /// Shallow mode: only auth files copied, everything else symlinked from host. + #[arg(long)] + shallow: bool, }, /// List isolated profiles. Ls { @@ -439,7 +445,7 @@ fn main() { }, Commands::Auth { action } => match action { AuthAction::Backup { agent, profile, json } => auth::backup(&agent, &profile, json), - AuthAction::Activate { agent, profile, json } => auth::activate(&agent, &profile, json), + AuthAction::Activate { agent, profile, json, reload_daemon } => auth::activate_with(&agent, &profile, reload_daemon, json), AuthAction::Status { agent, json } => auth::status(agent.as_deref(), json), AuthAction::Catalog { json } => auth::list_agents(json), AuthAction::Ls { agent, json } => auth::ls(&agent, json), @@ -461,7 +467,7 @@ fn main() { }, AuthAction::Run { agent, json, args } => auth_runner::run(&agent, &args, json), AuthAction::Isolate { action } => match action { - IsolateAction::Add { agent, profile, json } => auth::isolate_add(&agent, &profile, json), + IsolateAction::Add { agent, profile, json, shallow } => auth::isolate_add_with(&agent, &profile, shallow, json), IsolateAction::Ls { agent, json } => auth::isolate_ls(agent.as_deref(), json), IsolateAction::Delete { agent, profile, json } => auth::isolate_delete(&agent, &profile, json), },