-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli-pre-run.sh
More file actions
224 lines (181 loc) · 6.47 KB
/
cli-pre-run.sh
File metadata and controls
224 lines (181 loc) · 6.47 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
# --- AI CLI wrappers: optional worktree + auto-branch + approval bypass -----
# Short commands:
# cdx -> codex
# cld -> claude
# gmi -> gemini
#
# Behavior:
# - If NOT in a git repo:
# -> run tool here
# - If in a git repo:
# -> show git state (branch, clean)
# -> prompt:
# k = keep current (no worktree)
# w = create + checkout new worktree (prompts for branch name)
# s = stop
# -------- config ------------------------------------------------------------
: "${AI_WORKTREE_ROOT:=../worktrees}"
: "${AI_CLAUDE_FLAGS:=}"
: "${AI_GEMINI_FLAGS:=}"
: "${AI_CODEX_FLAGS:=}"
# -------- color + logging ---------------------------------------------------
_ai_is_tty() { [[ -t 2 ]]; }
_ai_c_reset=$'\033[0m'
_ai_c_dim=$'\033[2m'
_ai_c_red=$'\033[31m'
_ai_c_green=$'\033[32m'
_ai_c_yellow=$'\033[33m'
_ai_c_blue=$'\033[34m'
_ai_c_magenta=$'\033[35m'
_ai_c_cyan=$'\033[36m'
_ai_c_bold=$'\033[1m'
_ai_color() {
local c="$1"; shift
if _ai_is_tty; then printf '%s%s%s' "$c" "$*" "$_ai_c_reset"
else printf '%s' "$*"
fi
}
_ai_log() { printf '%s\n' "$(_ai_color "$_ai_c_dim" "•") $*" >&2; }
_ai_info() { printf '%s\n' "$(_ai_color "$_ai_c_cyan" "→") $*" >&2; }
_ai_ok() { printf '%s\n' "$(_ai_color "$_ai_c_green" "✓") $*" >&2; }
_ai_warn() { printf '%s\n' "$(_ai_color "$_ai_c_yellow" "!") $*" >&2; }
_ai_err() { printf '%s\n' "$(_ai_color "$_ai_c_red" "✗") $*" >&2; }
# -------- git helpers -------------------------------------------------------
_ai_in_git_repo() {
git rev-parse --is-inside-work-tree >/dev/null 2>&1
}
_ai_git_branch() {
git symbolic-ref --quiet --short HEAD 2>/dev/null || true
}
_ai_git_is_clean() {
git diff --quiet --ignore-submodules -- &&
git diff --cached --quiet --ignore-submodules --
}
# -------- misc helpers ------------------------------------------------------
_ai_slug_from_args() {
local s="$*"
s="$(printf '%s' "$s" | tr '[:upper:]' '[:lower:]')"
s="$(printf '%s' "$s" | tr -cs 'a-z0-9' '-' | sed -e 's/^-//' -e 's/-$//')"
printf '%s' "${s:0:40}"
}
_ai_prompt_branch_name() {
local suggested="$1"
local name
_ai_info "Creating a new branch + worktree"
_ai_log "Suggested branch: $(_ai_color "$_ai_c_magenta" "$suggested")"
read -r -p "Branch name (enter to accept): " name >&2
[[ -z "$name" ]] && name="$suggested"
git check-ref-format --branch "$name" >/dev/null 2>&1 || {
_ai_err "Invalid branch name: $name"
return 1
}
printf '%s' "$name"
}
_ai_worktree_dir_for_branch() {
printf '%s' "$1" | tr '/:' '__'
}
_ai_next_free_dir() {
local base="$1"
local dir="$base"
local i=1
while [[ -e "$dir" ]]; do
dir="${base}-${i}"
i=$((i+1))
done
printf '%s' "$dir"
}
_ai_create_worktree_and_run() {
# args: tool base_ref ...tool_args
local tool="$1"; shift
local base_ref="$1"; shift
local ts slug suggested new_branch wt_root wt_dir_base wt_dir
ts="$(date +%Y%m%d-%H%M%S)"
slug="$(_ai_slug_from_args "$@")"
[[ -z "$slug" ]] && slug="prompt"
suggested="ai/${tool}/${ts}-${slug}"
new_branch="$(_ai_prompt_branch_name "$suggested")" || return $?
wt_root="$AI_WORKTREE_ROOT"
mkdir -p "$wt_root" || return $?
wt_dir_base="${wt_root}/$(_ai_worktree_dir_for_branch "$new_branch")"
wt_dir="$(_ai_next_free_dir "$wt_dir_base")"
_ai_info "Creating worktree…"
_ai_log "base : $(_ai_color "$_ai_c_magenta" "$base_ref")"
_ai_log "branch : $(_ai_color "$_ai_c_magenta" "$new_branch")"
_ai_log "dir : $(_ai_color "$_ai_c_magenta" "$wt_dir")"
git worktree add -b "$new_branch" "$wt_dir" "$base_ref" || return $?
_ai_ok "Worktree created"
_ai_info "Running $(_ai_color "$_ai_c_bold" "$tool") inside worktree"
( cd "$wt_dir" && command "$tool" "$@" )
}
# -------- tool resolution & flags ------------------------------------------
_ai_resolve_tool() {
case "$1" in
cdx) printf '%s' codex ;;
cld) printf '%s' claude ;;
gmi) printf '%s' gemini ;;
codex|claude|gemini) printf '%s' "$1" ;;
*) printf '%s' "$1" ;;
esac
}
_ai_tool_flags() {
case "$1" in
claude) printf '%s' "$AI_CLAUDE_FLAGS" ;;
gemini) printf '%s' "$AI_GEMINI_FLAGS" ;;
codex) printf '%s' "$AI_CODEX_FLAGS" ;;
*) printf '%s' "" ;;
esac
}
# -------- core runner -------------------------------------------------------
_ai_run_with_optional_worktree() {
local tool="$1"; shift
if ! _ai_in_git_repo; then
_ai_warn "Not in a git repo; running $(_ai_color "$_ai_c_bold" "$tool") here."
command "$tool" "$@"
return $?
fi
local br clean
br="$(_ai_git_branch)"
clean=0
_ai_git_is_clean && clean=1
printf '%s\n' "$(_ai_color "$_ai_c_blue" "Git state:")" >&2
printf ' %s %s\n' "$(_ai_color "$_ai_c_dim" "branch:")" "$(_ai_color "$_ai_c_magenta" "${br:-"(detached)"}")" >&2
printf ' %s %s\n' "$(_ai_color "$_ai_c_dim" "clean :")" "$([[ $clean -eq 1 ]] && _ai_color "$_ai_c_green" yes || _ai_color "$_ai_c_yellow" no)" >&2
local ans
read -r -p "Choose: [k=keep current, w=create+checkout worktree, s=stop]: " ans
if [[ "$ans" =~ ^[Ss]$ ]]; then
_ai_warn "Stopped."
return 1
fi
if [[ "$ans" =~ ^[Ww]$ ]]; then
local base_ref
# If detached, base on HEAD; otherwise base on current branch.
base_ref="${br:-HEAD}"
if (( ! clean )); then
_ai_warn "Working tree is dirty; worktree will NOT include uncommitted changes."
fi
_ai_create_worktree_and_run "$tool" "$base_ref" "$@"
return $?
fi
[[ "$ans" =~ ^[Kk]$ ]] || return 1
_ai_info "Running $(_ai_color "$_ai_c_bold" "$tool") on current worktree (no new worktree)."
command "$tool" "$@"
}
# -------- default flags wrapper --------------------------------------------
_ai_run_with_default_flags() {
local short="$1"; shift
local tool flags_str
local -a flags=()
tool="$(_ai_resolve_tool "$short")"
flags_str="$(_ai_tool_flags "$tool")"
[[ -n "$flags_str" ]] && read -r -a flags <<<"$flags_str"
_ai_run_with_optional_worktree "$tool" "${flags[@]}" "$@"
}
# -------- commands ----------------------------------------------------------
cdx() { _ai_run_with_default_flags cdx "$@"; }
cld() { _ai_run_with_default_flags cld "$@"; }
gmi() { _ai_run_with_default_flags gmi "$@"; }
# optional: keep long names (and keep bypass flags behavior here)
codex() { _ai_run_with_default_flags codex "$@" --full-auto; }
claude() { _ai_run_with_default_flags claude "$@" --permission-mode bypassPermissions; }
gemini() { _ai_run_with_default_flags gemini "$@" -y -s; }
# ---------------------------------------------------------------------------