-
-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathcopilot_here.sh
More file actions
executable file
·431 lines (377 loc) · 12.6 KB
/
copilot_here.sh
File metadata and controls
executable file
·431 lines (377 loc) · 12.6 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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# copilot_here shell functions
# Version: 2026.03.30
# Repository: https://github.com/GordonBeeming/copilot_here
# Configuration
COPILOT_HERE_BIN="${COPILOT_HERE_BIN:-$HOME/.local/bin/copilot_here}"
COPILOT_HERE_RELEASE_URL="https://github.com/GordonBeeming/copilot_here/releases/download/cli-latest"
COPILOT_HERE_VERSION="2026.04.09"
# Ensure user bin directory is on PATH (required for the native binary + shell integration checks)
if [ -d "$HOME/.local/bin" ]; then
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) export PATH="$HOME/.local/bin:$PATH" ;;
esac
fi
# Debug logging function
__copilot_debug() {
if [ "$COPILOT_HERE_DEBUG" = "1" ] || [ "$COPILOT_HERE_DEBUG" = "true" ]; then
echo "[DEBUG] $*" >&2
fi
}
# Returns 0 if version A is newer than version B
__copilot_version_is_newer() {
awk -v a="$1" -v b="$2" '
function pad(arr, n, i) { for (i = n + 1; i <= 4; i++) arr[i] = 0 }
BEGIN {
na = split(a, A, ".");
nb = split(b, B, ".");
pad(A, na);
pad(B, nb);
for (i = 1; i <= 4; i++) {
if ((A[i] + 0) > (B[i] + 0)) exit 0;
if ((A[i] + 0) < (B[i] + 0)) exit 1;
}
exit 1;
}' >/dev/null 2>&1
}
# Helper function to stop running containers with confirmation
__copilot_stop_containers() {
local running_containers
running_containers=$(docker ps --filter "name=copilot_here-" -q 2>/dev/null)
if [ -n "$running_containers" ]; then
echo "⚠️ copilot_here is currently running in Docker"
printf " Stop running containers to continue? [y/N]: "
read -r response
local lower_response
lower_response=$(echo "$response" | tr '[:upper:]' '[:lower:]')
if [ "$lower_response" = "y" ] || [ "$lower_response" = "yes" ]; then
echo "🛑 Stopping copilot_here containers..."
docker stop $running_containers 2>/dev/null
echo " ✓ Stopped"
return 0
else
echo "❌ Cannot update while containers are running (binary is in use)"
return 1
fi
fi
return 0
}
# Helper function to download and install binary
__copilot_download_binary() {
__copilot_debug "Downloading binary..."
# Detect OS and architecture
local os=""
local arch=""
case "$(uname -s)" in
Linux*) os="linux" ;;
Darwin*) os="osx" ;;
*) echo "❌ Unsupported OS: $(uname -s)"; return 1 ;;
esac
case "$(uname -m)" in
x86_64) arch="x64" ;;
aarch64|arm64) arch="arm64" ;;
*) echo "❌ Unsupported architecture: $(uname -m)"; return 1 ;;
esac
__copilot_debug "OS: $os, Arch: $arch"
# Create bin directory
local bin_dir
bin_dir="$(dirname "$COPILOT_HERE_BIN")"
mkdir -p "$bin_dir"
# Download latest release archive
local download_url="${COPILOT_HERE_RELEASE_URL}/copilot_here-${os}-${arch}.tar.gz"
local tmp_archive
tmp_archive="$(mktemp)"
echo "📦 Downloading binary from: $download_url"
if ! curl -fsSL "$download_url" -o "$tmp_archive"; then
rm -f "$tmp_archive"
echo "❌ Failed to download binary"
return 1
fi
# Extract binary from archive
if ! tar -xzf "$tmp_archive" -C "$bin_dir" copilot_here; then
rm -f "$tmp_archive"
echo "❌ Failed to extract binary"
return 1
fi
rm -f "$tmp_archive"
chmod +x "$COPILOT_HERE_BIN"
echo "✅ Binary installed to: $COPILOT_HERE_BIN"
return 0
}
# Helper function to ensure binary is installed
__copilot_ensure_binary() {
__copilot_debug "Checking for binary at: $COPILOT_HERE_BIN"
if [ ! -f "$COPILOT_HERE_BIN" ]; then
echo "📥 copilot_here binary not found. Installing..."
__copilot_download_binary
else
__copilot_debug "Binary found"
fi
return 0
}
# Update function - downloads fresh shell script and binary
__copilot_update() {
echo "🔄 Updating copilot_here..."
# Check and stop running containers
if ! __copilot_stop_containers; then
return 1
fi
# Download fresh binary
echo ""
echo "📥 Downloading latest binary..."
if [ -f "$COPILOT_HERE_BIN" ]; then
rm -f "$COPILOT_HERE_BIN"
fi
if ! __copilot_download_binary; then
echo "❌ Failed to download binary"
return 1
fi
# Download and source fresh shell script
echo ""
echo "📥 Downloading latest shell script..."
local script_path="$HOME/.copilot_here.sh"
local tmp_script
tmp_script="$(mktemp)"
if curl -fsSL "${COPILOT_HERE_RELEASE_URL}/copilot_here.sh" -o "$tmp_script" 2>/dev/null; then
if cat "$tmp_script" > "$script_path" 2>/dev/null; then
rm -f "$tmp_script"
# Update shell profiles with marker blocks
echo ""
echo "🔧 Updating shell profiles..."
__copilot_update_profile "$HOME/.bashrc" "bash (.bashrc)"
__copilot_update_profile "$HOME/.zshrc" "zsh (.zshrc)"
echo "✅ Profiles updated"
echo "✅ Update complete! Reloading shell functions..."
# shellcheck disable=SC1090
source "$script_path"
echo ""
echo "[VERSION] Script: $COPILOT_HERE_VERSION"
if [ -x "$COPILOT_HERE_BIN" ]; then
BIN_VERSION=$($COPILOT_HERE_BIN --version 2>/dev/null | head -n 1)
if [ -n "$BIN_VERSION" ]; then
echo "[VERSION] Binary: $BIN_VERSION"
fi
fi
echo ""
else
rm -f "$tmp_script"
echo "✅ Binary updated!"
echo ""
echo "⚠️ Could not write updated shell script to: $script_path"
echo " Please re-source manually:"
echo " source <(curl -fsSL ${COPILOT_HERE_RELEASE_URL}/copilot_here.sh)"
echo ""
echo " Or restart your terminal."
fi
else
rm -f "$tmp_script"
echo ""
echo "✅ Binary updated!"
echo ""
echo "⚠️ Could not auto-reload shell functions. Please re-source manually:"
echo " source <(curl -fsSL ${COPILOT_HERE_RELEASE_URL}/copilot_here.sh)"
echo ""
echo " Or restart your terminal."
fi
return 0
}
# Helper function to update a profile file with marker blocks
__copilot_update_profile() {
local profile_path="$1"
local profile_name="$2"
local script_path="$HOME/.copilot_here.sh"
# Create profile if it doesn't exist
if [ ! -f "$profile_path" ]; then
touch "$profile_path"
fi
local marker_start="# >>> copilot_here >>>"
local marker_end="# <<< copilot_here <<<"
local temp_file
temp_file=$(mktemp)
# Check if marker block exists
if grep -qF "$marker_start" "$profile_path" 2>/dev/null; then
# Remove the entire marker block and rebuild fresh
awk -v start="$marker_start" -v end="$marker_end" '
BEGIN { in_block=0 }
$0 ~ start { in_block=1; next }
$0 ~ end { in_block=0; next }
!in_block && $0 !~ /copilot_here\.sh/ { print }
' "$profile_path" > "$temp_file"
else
# No markers - remove rogue entries
grep -v "copilot_here.sh" "$profile_path" > "$temp_file" 2>/dev/null || cat "$profile_path" > "$temp_file" 2>/dev/null || true
fi
# Add fresh marker block
cat >> "$temp_file" << EOF
$marker_start
# Ensure user bin directory is on PATH
if [ -d "$HOME/.local/bin" ]; then
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) export PATH="$HOME/.local/bin:$PATH" ;;
esac
fi
if [ -f "$script_path" ]; then
source "$script_path"
fi
$marker_end
EOF
# Preserve symlinks: if target is a symlink, mv into the resolved path atomically;
# otherwise write through the path to handle regular files too.
local real_path
if [ -L "$profile_path" ]; then
real_path="$(readlink -f "$profile_path")"
mv "$temp_file" "$real_path"
else
if ! cat "$temp_file" > "$profile_path"; then
echo " ✗ Failed to write $profile_name" >&2
rm -f "$temp_file"
return 1
fi
rm "$temp_file"
fi
echo " ✓ $profile_name"
}
# Reset function - same as update (kept for backwards compatibility)
__copilot_reset() {
__copilot_update
}
# Check for updates (called at startup)
__copilot_check_for_updates() {
__copilot_debug "Checking for updates..."
# Fetch remote version with 2 second timeout
local remote_version
remote_version=$(curl -m 2 -fsSL "${COPILOT_HERE_RELEASE_URL}/copilot_here.sh" 2>/dev/null | sed -n 's/^COPILOT_HERE_VERSION="\(.*\)"$/\1/p')
if [ -z "$remote_version" ]; then
__copilot_debug "Could not fetch remote version (offline or timeout)"
return 0 # Failed to check or offline - continue normally
fi
__copilot_debug "Local version: $COPILOT_HERE_VERSION, Remote version: $remote_version"
if [ "$COPILOT_HERE_VERSION" != "$remote_version" ]; then
# Check if remote is actually newer using sort -V
local newest
newest=$(printf "%s\n%s" "$COPILOT_HERE_VERSION" "$remote_version" | sort -V | tail -n1)
if [ "$newest" = "$remote_version" ]; then
echo "📢 Update available: $COPILOT_HERE_VERSION → $remote_version"
printf "Would you like to update now? [y/N]: "
read -r confirmation
local lower_confirmation
lower_confirmation=$(echo "$confirmation" | tr '[:upper:]' '[:lower:]')
if [ "$lower_confirmation" = "y" ] || [ "$lower_confirmation" = "yes" ]; then
__copilot_update
return 1 # Signal that update was performed
fi
fi
fi
return 0
}
# Check if argument is an update command
__copilot_is_update_arg() {
case "$1" in
--update|-u|--upgrade|--update-scripts|--upgrade-scripts)
return 0
;;
*)
return 1
;;
esac
}
# Check if argument is a reset command
__copilot_is_reset_arg() {
case "$1" in
--reset)
return 0
;;
*)
return 1
;;
esac
}
# Safe Mode: Asks for confirmation before executing
copilot_here() {
__copilot_debug "=== copilot_here called with args: $*"
# Check if script file version differs from in-memory version
local script_path="$HOME/.copilot_here.sh"
if [ -f "$script_path" ]; then
local file_version
file_version=$(grep '^COPILOT_HERE_VERSION=' "$script_path" 2>/dev/null | sed -n 's/^COPILOT_HERE_VERSION="\(.*\)"$/\1/p')
if [ -n "$file_version" ] && __copilot_version_is_newer "$file_version" "$COPILOT_HERE_VERSION"; then
__copilot_debug "Newer on-disk script detected: in-memory=$COPILOT_HERE_VERSION, file=$file_version"
echo "🔄 Detected updated shell script (v$file_version), reloading..."
source "$script_path"
copilot_here "$@"
return $?
fi
fi
# Handle --update and variants before binary check
local arg
for arg in "$@"; do
if __copilot_is_update_arg "$arg"; then
__copilot_debug "Update argument detected"
__copilot_update
return $?
fi
done
# Handle --reset before binary check
for arg in "$@"; do
if __copilot_is_reset_arg "$arg"; then
__copilot_debug "Reset argument detected"
__copilot_reset
return $?
fi
done
# Check for updates at startup
__copilot_debug "Checking for updates..."
__copilot_check_for_updates || return 0
__copilot_debug "Ensuring binary is installed..."
__copilot_ensure_binary || return 1
__copilot_debug "Executing binary: $COPILOT_HERE_BIN $*"
"$COPILOT_HERE_BIN" "$@"
local exit_code=$?
__copilot_debug "Binary exited with code: $exit_code"
return $exit_code
}
# YOLO Mode: Auto-approves all tool usage
copilot_yolo() {
__copilot_debug "=== copilot_yolo called with args: $*"
# Check if script file version differs from in-memory version
local script_path="$HOME/.copilot_here.sh"
if [ -f "$script_path" ]; then
local file_version
file_version=$(grep '^COPILOT_HERE_VERSION=' "$script_path" 2>/dev/null | sed -n 's/^COPILOT_HERE_VERSION="\(.*\)"$/\1/p')
if [ -n "$file_version" ] && __copilot_version_is_newer "$file_version" "$COPILOT_HERE_VERSION"; then
__copilot_debug "Newer on-disk script detected: in-memory=$COPILOT_HERE_VERSION, file=$file_version"
echo "🔄 Detected updated shell script (v$file_version), reloading..."
source "$script_path"
copilot_yolo "$@"
return $?
fi
fi
# Handle --update and variants before binary check
local arg
for arg in "$@"; do
if __copilot_is_update_arg "$arg"; then
__copilot_debug "Update argument detected"
__copilot_update
return $?
fi
done
# Handle --reset before binary check
for arg in "$@"; do
if __copilot_is_reset_arg "$arg"; then
__copilot_debug "Reset argument detected"
__copilot_reset
return $?
fi
done
# Check for updates at startup
__copilot_debug "Checking for updates..."
__copilot_check_for_updates || return 0
__copilot_debug "Ensuring binary is installed..."
__copilot_ensure_binary || return 1
__copilot_debug "Executing binary in YOLO mode: $COPILOT_HERE_BIN --yolo $*"
"$COPILOT_HERE_BIN" --yolo "$@"
local exit_code=$?
__copilot_debug "Binary exited with code: $exit_code"
return $exit_code
}