1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! CLI entry point: argument parsing, logging init, command dispatch.
use clap::{Parser, Subcommand};
use log::{LevelFilter, error, info, warn};
use simplelog::{Config, SimpleLogger};
use wanderlust::{cleaner, elevation};
/// The primary Command Line Interface (CLI) configuration.
///
/// Uses `clap` for sub-command parsing and help generation.
#[derive(Parser)]
#[command(name = "wanderlust")]
#[command(about = "A self-healing PATH manager for Windows", long_about = None)]
struct Cli {
/// The sub-command to execute (heal, doctor, install, etc.).
#[command(subcommand)]
command: Option<Commands>,
/// Turn on verbose logging.
///
/// - `-v`: Debug
/// - `-vv`: Trace
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
}
/// Available sub-commands for the Wanderlust utility.
#[derive(Subcommand)]
enum Commands {
/// Analyze and fix the PATH once.
///
/// This command will:
/// 1. Discover all tools on the system.
/// 2. Construct a minimal, deduplicated PATH.
/// 3. Update the Registry.
/// 4. Broadcast the change to the system.
Heal {
/// Dry run: don't actually change the registry, just print what would happen.
///
/// Useful for auditing what Wanderlust *would* do without risk.
#[arg(long)]
dry_run: bool,
},
/// Inspect the PATH and report issues.
///
/// Checks for:
/// - Duplicate entries.
/// - Broken paths (directories that don't exist).
/// - Shadowed commands.
Doctor,
/// Install as a scheduled task (runs every 30 minutes).
///
/// This creates a Windows Scheduled Task running with highest privileges.
Install,
/// Uninstall the scheduled task.
///
/// Removes the `WanderlustHeal` task from the scheduler.
Uninstall,
}
fn main() {
let cli = Cli::parse();
// Determine log level based on verbosity flag
let log_level = match cli.verbose {
0 => LevelFilter::Info,
1 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
// Initialize logger
// We ignore the result here as logging failure shouldn't crash the startup
let _ = SimpleLogger::init(log_level, Config::default());
match &cli.command {
Some(Commands::Heal { dry_run }) => {
// User PATH (HKCU) does NOT require elevation - normal user can write to it
// System PATH (HKLM) requires Admin, but we handle that gracefully in clean_system_path
// So we just run directly - no elevation needed for the common case
if let Err(e) = cleaner::heal_path(*dry_run) {
error!("Failed to heal PATH: {}", e);
std::process::exit(1);
}
}
Some(Commands::Doctor) => {
if let Err(e) = cleaner::doctor() {
error!("Doctor check failed: {}", e);
}
}
Some(Commands::Install) => {
// Installation strictly requires Admin rights to modify Scheduled Tasks.
if !elevation::is_elevated() {
warn!("Installation requires admin rights. Attempting to elevate...");
if elevation::relaunch_as_admin() {
return;
}
error!("Elevation failed. Installation will likely fail.");
}
// Reliable way to get the absolute path of the currently running binary.
let exe_path = std::env::current_exe()
.unwrap_or_else(|_| std::path::PathBuf::from("wanderlust.exe"));
let exe_str = exe_path.to_string_lossy();
info!("Installing scheduled task 'WanderlustHeal'...");
// Create a scheduled task that runs "wanderlust heal" every 30 minutes.
//
// TRICK: We wrap the call in PowerShell with `-WindowStyle Hidden`.
// By default, scheduled tasks might flash a console window. This wrapper prevents that annoyance.
//
// Arguments breakdown:
// /SC MINUTE /MO 30 -> Schedule every 30 minutes
// /RL HIGHEST -> Run with highest privileges (Admin)
// /NP -> No Password required (can run non-interactively)
// /F -> Force create (overwrite existing)
let arg_command = format!(
"powershell -WindowStyle Hidden -Command '& \"{}\" heal'",
exe_str
);
let status = std::process::Command::new("schtasks")
.arg("/Create")
.arg("/SC")
.arg("MINUTE")
.arg("/MO")
.arg("30")
.arg("/TN")
.arg("WanderlustHeal")
.arg("/TR")
.arg(arg_command)
.arg("/F")
.arg("/RL")
.arg("HIGHEST")
.arg("/NP")
.status();
match status {
Ok(s) if s.success() => info!(
"Successfully installed scheduled task. Wanderlust will run every 30 minutes (hidden)."
),
Ok(s) => error!("Failed to install task. Exit code: {:?}", s.code()),
Err(e) => error!("Failed to execute schtasks: {}", e),
}
}
Some(Commands::Uninstall) => {
info!("Uninstalling scheduled task 'WanderlustHeal'...");
let status = std::process::Command::new("schtasks")
.arg("/Delete")
.arg("/TN")
.arg("WanderlustHeal")
.arg("/F")
.status();
match status {
Ok(s) if s.success() => info!("Successfully uninstalled scheduled task."),
Ok(s) => error!(
"Failed to uninstall task (maybe it doesn't exist?). Exit code: {:?}",
s.code()
),
Err(e) => error!("Failed to execute schtasks: {}", e),
}
}
None => {
// Default behavior if no command: print the help message
use clap::CommandFactory;
let _ = Cli::command().print_help();
}
}
}