-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathagentree-v0.1.sh
More file actions
executable file
·283 lines (242 loc) · 9.08 KB
/
agentree-v0.1.sh
File metadata and controls
executable file
·283 lines (242 loc) · 9.08 KB
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
#!/usr/bin/env bash
# ----------------------------------------------------------------------------
# agentree — create *or* remove isolated Git worktrees for agentic workflows
# ----------------------------------------------------------------------------
# agentree -b <name> → create worktree on agent/<name>
# agentree rm <branch|path> [-y] [-R] → remove worktree (and optionally branch)
# ----------------------------------------------------------------------------
# CREATION FLAGS
# -b <name|branch> Name after agent/ OR full branch path if it contains '/'
# -f <base> Base branch to fork from (default: current HEAD)
# -p Push new branch to origin after creation
# -r Create a GitHub PR via gh CLI after push (implies -p)
# -d <dest> Override destination directory
# -e Copy .env and .dev.vars files from source worktree
# -s Run setup scripts (auto-detect or from config)
# -S <script> Run custom post-create script (can use multiple times)
#
# REMOVAL FLAGS (after `rm` sub‑command)
# -y No‑prompt force removal (implies --force to Git)
# -R Also delete the local branch after removing worktree
# ----------------------------------------------------------------------------
# INSTALL:
# chmod +x ~/bin/agentree && echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc
# ----------------------------------------------------------------------------
set -euo pipefail
# ─── Helper ------------------------------------------------------------------
# Load configuration from file
load_config() {
local config_file="$1"
[[ -f "$config_file" ]] && source "$config_file"
}
# Auto-detect package manager and return setup commands
auto_detect_setup() {
local dir="$1"
local scripts=()
# Node.js package managers
if [[ -f "$dir/pnpm-lock.yaml" ]]; then
scripts+=("pnpm install")
[[ -f "$dir/package.json" ]] && grep -q '"build"' "$dir/package.json" && scripts+=("pnpm build")
elif [[ -f "$dir/package-lock.json" ]]; then
scripts+=("npm install")
[[ -f "$dir/package.json" ]] && grep -q '"build"' "$dir/package.json" && scripts+=("npm run build")
elif [[ -f "$dir/yarn.lock" ]]; then
scripts+=("yarn install")
[[ -f "$dir/package.json" ]] && grep -q '"build"' "$dir/package.json" && scripts+=("yarn build")
# Other languages
elif [[ -f "$dir/Cargo.lock" ]]; then
scripts+=("cargo build")
elif [[ -f "$dir/go.mod" ]]; then
scripts+=("go mod download")
elif [[ -f "$dir/requirements.txt" ]]; then
scripts+=("pip install -r requirements.txt")
elif [[ -f "$dir/Gemfile.lock" ]]; then
scripts+=("bundle install")
fi
printf '%s\n' "${scripts[@]}"
}
# Run post-create scripts
run_post_scripts() {
local dest="$1"
shift
local scripts=("$@")
if [[ ${#scripts[@]} -eq 0 ]]; then
return
fi
echo "🚀 Running post-create scripts..."
cd "$dest"
for script in "${scripts[@]}"; do
echo " → $script"
if eval "$script"; then
echo " ✓ Success"
else
echo " ✗ Failed: $script" >&2
fi
done
}
usage() {
cat <<EOF
Usage: agentree -b <name> [options] # create worktree
agentree rm <branch|path> [flags] # remove worktree
Creation options:
-b <name> required; branch name (prefixes agent/ if no slash)
-f <base> base branch (default: current HEAD)
-p push branch to origin
-r create GitHub PR (implies -p)
-d <dest> custom destination path
-e copy .env and .dev.vars files from source
-s run setup scripts (auto-detect or from config)
-S <script> run custom post-create script (repeatable)
Removal flags (after 'rm'):
-y no confirmation; force if dirty
-R delete the local branch too
EOF
exit 1
}
confirm() {
# $1 prompt
read -r -p "$1 [y/N] " ans; [[ $ans =~ ^[Yy]$ ]];
}
# ─── Sub‑command dispatch -----------------------------------------------------
subcmd="create"
if [[ "${1:-}" == "rm" || "${1:-}" == "remove" ]]; then
subcmd="remove"; shift; fi
# ──────────────────────────────────────────────────────────────────────────────
if [[ $subcmd == "remove" ]]; then
# Default removal flags
force=""; del_branch=false
# Parse flags: -y (force) -R (delete branch)
while getopts "yR" opt; do
case $opt in
y) force="--force" ;;
R) del_branch=true ;;
*) usage ;;
esac
done
shift $((OPTIND-1))
target="${1:-}"
[[ -z $target ]] && { echo "❌ agentree rm <branch|path> required" >&2; usage; }
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "❌ Not inside a Git repo" >&2; exit 1; fi
# Determine path and branch
path=""; branch=""
if [[ -d $target ]]; then
path=$(cd "$target" && pwd)
branch=$(git worktree list --porcelain |
awk -v p="$path" '$1=="worktree" && $2==p {getline; if($1=="branch") {sub("refs/heads/","",$2); print $2}}')
else
branch="$target"
path=$(git worktree list --porcelain |
awk -v b="$branch" '$1=="worktree" {p=$2} $1=="branch" {sub("refs/heads/","",$2); if($2==b) print p}')
fi
[[ -z $path ]] && { echo "❌ Worktree not found for $target" >&2; exit 1; }
# Confirm if not forced
if [[ -z $force ]] && ! confirm "Remove worktree at $path?"; then exit 0; fi
git worktree remove $force "$path"
echo "✅ Removed worktree $path"
if $del_branch && [[ -n $branch ]]; then
git branch -D "$branch" || true
echo "🗑️ Deleted branch $branch"
fi
exit 0
fi
# ─── Creation path -----------------------------------------------------------
# Defaults
branch=""; base=""; push=false; pr=false; custom_dest=""; copy_env=false
run_setup=false; custom_scripts=()
# Load global config
load_config "$HOME/.config/agentree/config"
while getopts "b:f:prd:esS:h" opt; do
case $opt in
b) branch="$OPTARG" ;;
f) base="$OPTARG" ;;
p) push=true ;;
r) pr=true; push=true ;;
d) custom_dest="$OPTARG" ;;
e) copy_env=true ;;
s) run_setup=true ;;
S) custom_scripts+=("$OPTARG") ;;
h|*) usage ;;
esac
done
[[ -z $branch ]] && { echo "❌ -b <name> is required" >&2; usage; }
# Prefix agent/ if no slash
if [[ $branch != */* ]]; then
branch="agent/$branch"
fi
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "❌ Run this inside a Git repository" >&2; exit 1; fi
git fetch --prune
root=$(git rev-parse --show-toplevel)
repo=$(basename "$root")
parent=$(dirname "$root")
workdir="${parent}/${repo}-worktrees"
mkdir -p "$workdir"
# Resolve base
if [[ -z $base ]]; then
base=$(git symbolic-ref --quiet --short HEAD || git rev-parse --short HEAD)
fi
san_branch="${branch//\//-}"
dest="${custom_dest:-${workdir}/${san_branch}}"
if [[ -d $dest ]]; then
echo "❌ Destination $dest exists" >&2; exit 1; fi
if git show-ref --verify --quiet "refs/heads/$branch"; then
echo "❌ Branch $branch already exists" >&2; exit 1; fi
# Create branch + worktree
git branch "$branch" "$base"
git worktree add "$dest" "$branch"
echo "✅ Worktree ready:"
echo " path $dest"
echo " branch $branch (from $base)"
# Copy env files if requested
if $copy_env; then
for env_file in .env .dev.vars; do
if [[ -f "$root/$env_file" ]]; then
cp "$root/$env_file" "$dest/$env_file"
echo "📋 Copied $env_file"
fi
done
fi
# Run post-create scripts
if [[ ${#custom_scripts[@]} -gt 0 ]]; then
# Custom scripts take precedence
run_post_scripts "$dest" "${custom_scripts[@]}"
elif $run_setup; then
# Load project config if exists
POST_CREATE_SCRIPTS=()
load_config "$root/.agentreerc"
if [[ ${#POST_CREATE_SCRIPTS[@]} -gt 0 ]]; then
# Use project-specific scripts
run_post_scripts "$dest" "${POST_CREATE_SCRIPTS[@]}"
else
# Auto-detect setup commands
mapfile -t detected_scripts < <(auto_detect_setup "$dest")
# Check for user overrides in global config
if [[ ${#detected_scripts[@]} -gt 0 ]]; then
# Check for package manager specific overrides
if [[ "${detected_scripts[0]}" == "pnpm install" ]] && [[ -n "${PNPM_SETUP:-}" ]]; then
run_post_scripts "$dest" "$PNPM_SETUP"
elif [[ "${detected_scripts[0]}" == "npm install" ]] && [[ -n "${NPM_SETUP:-}" ]]; then
run_post_scripts "$dest" "$NPM_SETUP"
elif [[ "${detected_scripts[0]}" == "yarn install" ]] && [[ -n "${YARN_SETUP:-}" ]]; then
run_post_scripts "$dest" "$YARN_SETUP"
elif [[ -n "${DEFAULT_POST_CREATE:-}" ]]; then
run_post_scripts "$dest" "$DEFAULT_POST_CREATE"
else
run_post_scripts "$dest" "${detected_scripts[@]}"
fi
elif [[ -n "${DEFAULT_POST_CREATE:-}" ]]; then
run_post_scripts "$dest" "$DEFAULT_POST_CREATE"
fi
fi
fi
if $push; then
git -C "$dest" push -u origin "$branch"
fi
if $pr; then
if command -v gh >/dev/null 2>&1; then
gh -C "$dest" pr create --fill --web
else
echo "⚠️ gh CLI not found; skipping PR creation" >&2
fi
fi