dev_prune/commands/
setup.rs1use anyhow::Result;
11
12use crate::commands::hook::{self, HookState};
13use crate::config::Registry;
14use crate::daemon;
15use crate::output;
16use crate::setup;
17
18pub fn run(status_only: bool) -> Result<()> {
19 if status_only {
20 return run_status();
21 }
22
23 output::print_header("dev-prune setup");
24 let registry = Registry::load()?;
25 let report = setup::ensure_integrations(®istry, setup::Consent::Explicit);
26 report.print(true);
27 setup::suppress_next_auto_setup();
28 setup::record_consent_granted();
32
33 println!();
34 if report.needs_attention() {
35 output::print_info("Anything skipped above is optional — dev-prune works without it.");
36 } else {
37 output::print_success("Everything dev-prune needs is in place.");
38 }
39 output::print_info("Remove all of it again with `devp uninstall`.");
40
41 setup::offer_vscode_extension();
42
43 if registry.repositories.is_empty() {
48 println!();
49 output::print_info("No repositories are tracked yet. Register them either way:");
50 println!(
51 " devp init {} # crawl one folder for every Git repo inside it",
52 example_projects_dir()
53 );
54 println!(" devp link . # or, from inside one project, register just that one");
56 }
57
58 Ok(())
59}
60
61fn example_projects_dir() -> &'static str {
67 if cfg!(windows) { "~\\Code" } else { "~/code" }
68}
69
70fn run_status() -> Result<()> {
72 output::print_header("dev-prune setup status");
73 let registry = Registry::load()?;
74
75 let alias = std::env::current_exe()
76 .ok()
77 .and_then(|exe| exe.parent().map(|d| d.to_path_buf()))
78 .map(|dir| dir.join(if cfg!(windows) { "devp.exe" } else { "devp" }))
79 .filter(|p| p.exists());
80 println!(
81 " devp alias: {}",
82 alias
83 .as_ref()
84 .map(output::clean_path)
85 .unwrap_or_else(|| "not installed".to_string())
86 );
87
88 print!(" Command on PATH: ");
89 match setup::managed_bin_dir() {
90 Ok(dir) if crate::pathenv::is_reachable(&dir) => {
91 println!("{} is on your PATH", output::clean_path(&dir))
92 }
93 Ok(dir) => println!("{} is not on your PATH", output::clean_path(&dir)),
94 Err(_) => println!("unknown (no config directory)"),
95 }
96
97 let skill = setup::skill_path().ok().filter(|p| p.exists());
98 println!(
99 " SKILL.md: {}",
100 skill
101 .as_ref()
102 .map(output::clean_path)
103 .unwrap_or_else(|| "not exported".to_string())
104 );
105
106 let agent_roots = setup::agent_skill_roots();
107 if agent_roots.is_empty() {
108 println!(" AI agent skills: no AI agent detected");
109 } else {
110 for root in &agent_roots {
111 println!(
112 " AI agent skills: {} ({})",
113 output::clean_path(root),
114 if root.join("SKILL.md").is_file() {
115 "installed"
116 } else {
117 "not installed"
118 }
119 );
120 }
121 }
122
123 println!(
124 " File icons: {}",
125 if crate::commands::icon::is_registered() {
126 "registered"
127 } else {
128 "not registered"
129 }
130 );
131
132 print!(" Git hooks: ");
133 if !hook::git_available() {
134 println!("git is not on PATH");
135 } else {
136 match hook::state() {
137 Ok(HookState::Active) => println!("active"),
138 Ok(HookState::Absent) => println!("not installed"),
139 Ok(HookState::Chained { previous, drifted }) if drifted.is_empty() => {
140 println!("active, chained to `{previous}`")
141 }
142 Ok(HookState::Chained { previous, drifted }) => println!(
143 "active, chained to `{previous}` — {} not forwarded ({}); run `devp hook install --chain`",
144 drifted.len(),
145 drifted.join(", ")
146 ),
147 Ok(HookState::Foreign(p)) => println!(
148 "core.hooksPath belongs to `{p}` (install in front of it with `devp hook install --chain`)"
149 ),
150 Err(e) => println!("unknown ({e})"),
151 }
152 }
153
154 println!(
155 " Background scheduler: {}",
156 daemon::daemon_status()
157 .map(|s| s.to_string())
158 .unwrap_or_else(|e| format!("unknown ({e})"))
159 );
160
161 println!();
162 println!(" auto_setup = {}", registry.settings.auto_setup);
163 println!(" auto_hooks = {}", registry.settings.auto_hooks);
164 println!(" auto_daemon = {}", registry.settings.auto_daemon);
165
166 if let Some(why) = setup::unattended_environment() {
169 println!();
170 output::print_info(&format!(
171 "Unattended installation is off because {why}. `devp setup` still works when asked."
172 ));
173 }
174
175 println!();
176 if setup::setup_is_due() {
177 output::print_info("A setup pass is due. Run `devp setup`.");
178 } else {
179 output::print_info("Install anything missing with `devp setup`.");
180 }
181
182 Ok(())
183}