git_worktree_manager/operations/setup_claude/
mod.rs1use std::path::{Path, PathBuf};
8
9use console::style;
10
11use crate::constants::home_dir_or_fallback;
12use crate::error::Result;
13
14mod legacy;
15mod manifest;
16mod skill_delegate;
17mod skill_manage;
18
19const PLUGIN_NAME: &str = "gw";
20
21fn plugin_dir_under(home: &Path) -> PathBuf {
22 home.join(".claude").join("plugins").join(PLUGIN_NAME)
23}
24
25fn manifest_path_under(home: &Path) -> PathBuf {
26 plugin_dir_under(home).join("plugin.json")
27}
28fn delegate_skill_path_under(home: &Path) -> PathBuf {
29 plugin_dir_under(home)
30 .join("skills")
31 .join("delegate")
32 .join("SKILL.md")
33}
34fn manage_skill_path_under(home: &Path) -> PathBuf {
35 plugin_dir_under(home)
36 .join("skills")
37 .join("manage")
38 .join("SKILL.md")
39}
40fn manage_reference_path_under(home: &Path) -> PathBuf {
41 plugin_dir_under(home)
42 .join("skills")
43 .join("manage")
44 .join("references")
45 .join("gw-commands.md")
46}
47
48pub fn is_plugin_installed() -> bool {
50 manifest_path_under(&home_dir_or_fallback()).exists()
51}
52
53#[doc(hidden)]
54pub fn manage_skill_content_for_test() -> &'static str {
55 skill_manage::content()
56}
57
58#[doc(hidden)]
59pub fn manage_reference_content_for_test() -> &'static str {
60 skill_manage::reference_content()
61}
62
63pub fn is_skill_installed() -> bool {
66 is_plugin_installed() || legacy::any_legacy_present()
67}
68
69fn write_if_changed(
70 path: &PathBuf,
71 new_content: &str,
72) -> std::result::Result<bool, std::io::Error> {
73 if path.exists() {
74 let existing = std::fs::read_to_string(path).unwrap_or_default();
75 if existing == new_content {
76 return Ok(false);
77 }
78 }
79 if let Some(parent) = path.parent() {
80 std::fs::create_dir_all(parent)?;
81 }
82 std::fs::write(path, new_content)?;
83 Ok(true)
84}
85
86pub fn setup_claude() -> Result<()> {
87 setup_claude_under(&home_dir_or_fallback())
88}
89
90pub fn setup_claude_under(home: &Path) -> Result<()> {
95 legacy::remove_legacy_installs_under(home);
96
97 let manifest = manifest_path_under(home);
98 let delegate = delegate_skill_path_under(home);
99 let manage = manage_skill_path_under(home);
100 let reference = manage_reference_path_under(home);
101
102 let mut any_changed = false;
103 any_changed |= write_if_changed(&manifest, manifest::content())?;
104 any_changed |= write_if_changed(&delegate, skill_delegate::content())?;
105 any_changed |= write_if_changed(&manage, skill_manage::content())?;
106 any_changed |= write_if_changed(&reference, skill_manage::reference_content())?;
107
108 let location = plugin_dir_under(home);
109 if !any_changed {
110 println!("{} gw plugin already up to date.\n", style("*").green());
111 println!(" Location: {}", style(location.display()).dim());
112 return Ok(());
113 }
114
115 println!(
116 "{} gw plugin installed at {}.\n",
117 style("*").green().bold(),
118 style(location.display()).dim()
119 );
120 println!(
121 " Use {} in Claude Code to delegate tasks to worktrees.",
122 style("/gw").cyan()
123 );
124 println!(
125 " The bundled '{}' skill will recommend hooks (e.g. SessionStart sanity)",
126 style("manage").cyan()
127 );
128 println!(" in-session when relevant. It edits your project's .claude/settings.json");
129 println!(" on your consent — gw itself never modifies any settings file.\n");
130
131 Ok(())
132}