1use super::*;
4use serde::Deserialize;
5use std::process::Stdio;
6
7pub struct EnterWorktreeTool;
8
9#[async_trait]
10impl Tool for EnterWorktreeTool {
11 fn name(&self) -> &str {
12 "EnterWorktree"
13 }
14 fn description(&self) -> &str {
15 "Create an isolated git worktree for the agent to work in without affecting the main branch."
16 }
17 fn permission_level(&self) -> PermissionLevel {
18 PermissionLevel::Write
19 }
20 fn category(&self) -> ToolCategory {
21 ToolCategory::FileSystem
22 }
23
24 fn input_schema(&self) -> Value {
25 serde_json::json!({
26 "type": "object",
27 "properties": {
28 "branch": { "type": "string", "description": "Branch name for the worktree" },
29 "path": { "type": "string", "description": "Optional path for the worktree (default: auto-generated)" }
30 },
31 "required": ["branch"]
32 })
33 }
34
35 async fn execute(&self, input: Value, ctx: &ToolContext) -> ToolResult {
36 #[derive(Deserialize)]
37 struct Input {
38 branch: String,
39 path: Option<String>,
40 }
41
42 let input: Input = match serde_json::from_value(input) {
43 Ok(i) => i,
44 Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
45 };
46
47 let worktree_path = input.path.unwrap_or_else(|| {
48 let tmp = std::env::temp_dir().join(format!("cersei-wt-{}", &input.branch));
49 tmp.display().to_string()
50 });
51
52 let output = tokio::process::Command::new("git")
53 .args(["worktree", "add", "-b", &input.branch, &worktree_path])
54 .current_dir(&ctx.working_dir)
55 .stdout(Stdio::piped())
56 .stderr(Stdio::piped())
57 .output()
58 .await;
59
60 match output {
61 Ok(o) if o.status.success() => ToolResult::success(format!(
62 "Worktree created at: {}\nBranch: {}",
63 worktree_path, input.branch
64 )),
65 Ok(o) => {
66 let stderr = String::from_utf8_lossy(&o.stderr);
67 ToolResult::error(format!("git worktree failed: {}", stderr))
68 }
69 Err(e) => ToolResult::error(format!("Failed to run git: {}", e)),
70 }
71 }
72}
73
74pub struct ExitWorktreeTool;
75
76#[async_trait]
77impl Tool for ExitWorktreeTool {
78 fn name(&self) -> &str {
79 "ExitWorktree"
80 }
81 fn description(&self) -> &str {
82 "Remove a git worktree."
83 }
84 fn permission_level(&self) -> PermissionLevel {
85 PermissionLevel::Write
86 }
87 fn category(&self) -> ToolCategory {
88 ToolCategory::FileSystem
89 }
90
91 fn input_schema(&self) -> Value {
92 serde_json::json!({
93 "type": "object",
94 "properties": {
95 "path": { "type": "string", "description": "Path of the worktree to remove" }
96 },
97 "required": ["path"]
98 })
99 }
100
101 async fn execute(&self, input: Value, ctx: &ToolContext) -> ToolResult {
102 #[derive(Deserialize)]
103 struct Input {
104 path: String,
105 }
106
107 let input: Input = match serde_json::from_value(input) {
108 Ok(i) => i,
109 Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
110 };
111
112 let output = tokio::process::Command::new("git")
113 .args(["worktree", "remove", "--force", &input.path])
114 .current_dir(&ctx.working_dir)
115 .stdout(Stdio::piped())
116 .stderr(Stdio::piped())
117 .output()
118 .await;
119
120 match output {
121 Ok(o) if o.status.success() => {
122 ToolResult::success(format!("Worktree removed: {}", input.path))
123 }
124 Ok(o) => {
125 let stderr = String::from_utf8_lossy(&o.stderr);
126 ToolResult::error(format!("git worktree remove failed: {}", stderr))
127 }
128 Err(e) => ToolResult::error(format!("Failed to run git: {}", e)),
129 }
130 }
131}