Skip to content

Commit 1fcd689

Browse files
authored
Merge pull request #22 from trieloff/add-aider-support
feat: Add Aider CLI support with Ghostty integration
2 parents 7081387 + 900dc2f commit 1fcd689

3 files changed

Lines changed: 147 additions & 68 deletions

File tree

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ YOLO now automatically detects when it's running inside Kimi CLI and provides en
4848

4949
When YOLO detects it's running inside Kimi CLI, you'll see:
5050
```
51-
Detected Kimi CLI environment!
52-
YOLO is running inside Kimi CLI - enhanced integration enabled
51+
Detected Kimi CLI environment! YOLO is running inside Kimi CLI - enhanced integration enabled
5352
```
5453

5554
## Installation
@@ -217,6 +216,7 @@ Use `--mop` to clean up orphaned worktrees from interrupted sessions or when you
217216
| `copilot` | `--allow-all-tools --allow-all-paths` |
218217
| `droid` | *(no flags - prompt allowed positionally)* |
219218
| `amp` | `--dangerously-allow-all` |
219+
| `aider` | `--yes-always` |
220220
| `cursor-agent` | `--force` |
221221
| `gemini` | `--yolo` (+ `-i` when prompt present) |
222222
| `opencode` | *(no flags)* |
@@ -228,6 +228,13 @@ Use `--mop` to clean up orphaned worktrees from interrupted sessions or when you
228228
### Examples
229229

230230
```bash
231+
# Aider examples
232+
yolo aider "add authentication middleware" # Run aider with auto-approval
233+
yolo -w aider "implement feature" # Create worktree for aider session
234+
yolo -e aider # Compose complex prompt in editor for aider
235+
yolo -w -c aider "quick experiment" # Create worktree, auto-cleanup after
236+
yolo -w -nc aider "keep this work" # Create worktree, preserve it after
237+
231238
# Full YOLO mode - random agent selection
232239
yolo
233240
yolo -w # Random agent in a new worktree

executable_yolo

Lines changed: 97 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ Supported Commands:
103103
kimi Uses --yolo (requires Ghostty for interactive prompts)
104104
opencode No extra flags added
105105
crush Uses --yolo (requires Ghostty for interactive prompts)
106+
aider Uses --yes-always (requires Ghostty for interactive prompts)
106107
<other> Adds --yolo (generic fallback)
107108
108109
Multi-agent Mode:
@@ -134,6 +135,8 @@ Examples:
134135
yolo -w -nc claude "keep this" # Create worktree, preserve it after
135136
yolo -e codex,claude,gemini # Compose prompt in editor, run 3 agents in parallel
136137
yolo copilot chat # Run copilot chat with safety flags disabled
138+
yolo aider "add auth" # Run aider with auto-approval
139+
yolo -w aider "implement feature" # Create worktree, run aider with prompt
137140
138141
Worktree Mode:
139142
When using -w/--worktree flag:
@@ -293,6 +296,10 @@ get_command_flags() {
293296
# crush uses --yolo for auto-approval
294297
echo "--yolo"
295298
;;
299+
aider)
300+
# aider uses --yes-always for auto-approval
301+
echo "--yes-always"
302+
;;
296303
*)
297304
echo "--yolo"
298305
;;
@@ -307,7 +314,7 @@ command_exists() {
307314
# Get all installed supported coding agents
308315
get_installed_agents() {
309316
local agents=()
310-
local supported_commands=("codex" "claude" "copilot" "droid" "amp" "cursor-agent" "opencode" "qwen" "gemini" "kimi" "crush")
317+
local supported_commands=("codex" "claude" "copilot" "droid" "amp" "cursor-agent" "opencode" "qwen" "gemini" "kimi" "crush" "aider")
311318

312319
for cmd in "${supported_commands[@]}"; do
313320
if command_exists "$cmd"; then
@@ -769,14 +776,44 @@ run_multi_agents_ghostty() {
769776
return 0
770777
}
771778

779+
# Inject a prompt into the current Ghostty terminal after a delay (background)
780+
# Args: prompt_text, agent_name (for determining dialog dismissal behavior), delay_seconds (default 4)
781+
inject_prompt_background() {
782+
local prompt_text="$1"
783+
local agent_name="$2"
784+
local delay="${3:-4}"
785+
786+
(
787+
sleep "$delay"
788+
789+
# Handle agent-specific dialog dismissal
790+
case "$agent_name" in
791+
copilot)
792+
# Copilot needs 2 returns to dismiss dialog
793+
osascript -e 'tell application "System Events" to tell process "Ghostty" to keystroke return' >/dev/null 2>&1
794+
sleep 0.5
795+
osascript -e 'tell application "System Events" to tell process "Ghostty" to keystroke return' >/dev/null 2>&1
796+
sleep 1
797+
;;
798+
esac
799+
800+
# Send the prompt
801+
local escaped_prompt="$prompt_text"
802+
escaped_prompt="${escaped_prompt//\\/\\\\}"
803+
escaped_prompt="${escaped_prompt//\"/\\\"}"
804+
osascript -e "tell application \"System Events\" to tell process \"Ghostty\" to keystroke \"$escaped_prompt\"" >/dev/null 2>&1
805+
osascript -e 'tell application "System Events" to tell process "Ghostty" to keystroke return' >/dev/null 2>&1
806+
) &
807+
}
808+
772809
# Launch a single agent in a Ghostty split with prompt sent via AppleScript
773-
# This is used for agents like copilot and kimi that don't support interactive launching with a prompt
774-
# (Their prompt flags cause immediate exit after execution)
810+
# This is used in MULTI-AGENT mode for agents that don't support interactive launching with a prompt
775811
# Args: agent_cmd (launch command without prompt), prompt_text, is_copilot (optional, "true" if copilot)
776812
run_single_agent_ghostty_with_prompt() {
777813
local agent_cmd="$1"
778814
local prompt_text="$2"
779815
local is_copilot="${3:-false}"
816+
[[ "$agent_cmd" == *"copilot"* ]] && is_copilot=true
780817

781818
if ! is_ghostty; then
782819
print_error "not running in Ghostty terminal"
@@ -799,9 +836,12 @@ run_single_agent_ghostty_with_prompt() {
799836
print_debug "Waiting for agent to start..."
800837
sleep 4
801838

802-
# copilot shows a dialog that needs to be dismissed first
839+
# Handle agents that need initial dialog dismissal
803840
if [[ "$is_copilot" == "true" ]]; then
804841
print_debug "Dismissing copilot dialog..."
842+
# Copilot needs 2 returns to dismiss dialog
843+
osascript -e 'tell application "System Events" to tell process "Ghostty" to keystroke return' >/dev/null 2>&1
844+
sleep 0.5
805845
osascript -e 'tell application "System Events" to tell process "Ghostty" to keystroke return' >/dev/null 2>&1
806846
sleep 1
807847
fi
@@ -1505,63 +1545,42 @@ main() {
15051545
fi
15061546
;;
15071547
copilot)
1508-
if (( ${#command_args[@]} )); then
1548+
# Trust the current directory explicitly
1549+
local trust_dir
1550+
trust_dir=$(pwd)
1551+
1552+
if [[ -n "$flags" ]]; then
1553+
# shellcheck disable=SC2206
1554+
local flag_array=($flags)
1555+
full_command+=("${flag_array[@]}")
1556+
fi
1557+
full_command+=(--add-dir "$trust_dir")
1558+
1559+
# If prompt provided and in Ghostty, inject it in background
1560+
if (( ${#command_args[@]} )) && is_ghostty; then
15091561
local prompt_joined
15101562
prompt_joined=$(printf '%s ' "${command_args[@]}"); prompt_joined=${prompt_joined%% }
1511-
local flags_str=""
1512-
if [[ -n "$flags" ]]; then flags_str="$flags"; fi
1513-
# Trust the current directory explicitly
1514-
local trust_dir
1515-
trust_dir=$(pwd)
1516-
1517-
# Use Ghostty split method if available for cleaner interactive experience
1518-
if is_ghostty; then
1519-
local agent_cmd
1520-
agent_cmd="copilot $flags_str --add-dir $(printf %q \"$trust_dir\")"
1521-
run_single_agent_ghostty_with_prompt "$agent_cmd" "$prompt_joined" "true"
1522-
exit $?
1523-
else
1524-
# Fallback to one-shot + continue for non-Ghostty terminals
1525-
full_command=(bash -lc "copilot $flags_str --add-dir $(printf %q \"$trust_dir\") -p $(printf %q \"$prompt_joined\"); copilot $flags_str --add-dir $(printf %q \"$trust_dir\") --continue")
1526-
fi
1527-
else
1528-
if [[ -n "$flags" ]]; then
1529-
# shellcheck disable=SC2206
1530-
local flag_array=($flags)
1531-
full_command+=("${flag_array[@]}")
1532-
fi
1533-
# Trust current dir for interactive session
1534-
local trust_dir
1535-
trust_dir=$(pwd)
1536-
full_command+=(--add-dir "$trust_dir")
1563+
inject_prompt_background "$prompt_joined" "copilot" 4
15371564
fi
15381565
;;
15391566
kimi)
1540-
if (( ${#command_args[@]} )); then
1541-
local prompt_joined
1542-
prompt_joined=$(printf '%s ' "${command_args[@]}"); prompt_joined=${prompt_joined%% }
1543-
local flags_str=""
1544-
if [[ -n "$flags" ]]; then flags_str="$flags"; fi
1567+
if [[ -n "$flags" ]]; then
1568+
# shellcheck disable=SC2206
1569+
local flag_array=($flags)
1570+
full_command+=("${flag_array[@]}")
1571+
fi
15451572

1546-
# kimi --command exits after execution, not interactive
1547-
# Use Ghostty split method for proper interactive experience
1573+
# If prompt provided and in Ghostty, inject it in background
1574+
if (( ${#command_args[@]} )); then
15481575
if is_ghostty; then
1549-
local agent_cmd
1550-
agent_cmd="kimi $flags_str"
1551-
run_single_agent_ghostty_with_prompt "$agent_cmd" "$prompt_joined"
1552-
exit $?
1576+
local prompt_joined
1577+
prompt_joined=$(printf '%s ' "${command_args[@]}"); prompt_joined=${prompt_joined%% }
1578+
inject_prompt_background "$prompt_joined" "kimi" 4
15531579
else
15541580
print_error "kimi with prompts requires Ghostty terminal for interactive mode"
15551581
print_info "Either run in Ghostty or launch kimi without a prompt"
15561582
exit 1
15571583
fi
1558-
else
1559-
# No prompt - just launch interactively
1560-
if [[ -n "$flags" ]]; then
1561-
# shellcheck disable=SC2206
1562-
local flag_array=($flags)
1563-
full_command+=("${flag_array[@]}")
1564-
fi
15651584
fi
15661585
;;
15671586
opencode)
@@ -1590,30 +1609,42 @@ main() {
15901609
fi
15911610
;;
15921611
crush)
1593-
if (( ${#command_args[@]} )); then
1594-
local prompt_joined
1595-
prompt_joined=$(printf '%s ' "${command_args[@]}"); prompt_joined=${prompt_joined%% }
1596-
local flags_str=""
1597-
if [[ -n "$flags" ]]; then flags_str="$flags"; fi
1612+
if [[ -n "$flags" ]]; then
1613+
# shellcheck disable=SC2206
1614+
local flag_array=($flags)
1615+
full_command+=("${flag_array[@]}")
1616+
fi
15981617

1599-
# crush doesn't have -i flag, use delayed prompt sending like kimi
1600-
# Use Ghostty split method for proper interactive experience
1618+
# If prompt provided and in Ghostty, inject it in background
1619+
if (( ${#command_args[@]} )); then
16011620
if is_ghostty; then
1602-
local agent_cmd
1603-
agent_cmd="crush $flags_str"
1604-
run_single_agent_ghostty_with_prompt "$agent_cmd" "$prompt_joined"
1605-
exit $?
1621+
local prompt_joined
1622+
prompt_joined=$(printf '%s ' "${command_args[@]}"); prompt_joined=${prompt_joined%% }
1623+
inject_prompt_background "$prompt_joined" "crush" 4
16061624
else
16071625
print_error "crush with prompts requires Ghostty terminal for interactive mode"
16081626
print_info "Either run in Ghostty or launch crush without a prompt"
16091627
exit 1
16101628
fi
1611-
else
1612-
# No prompt - just launch interactively
1613-
if [[ -n "$flags" ]]; then
1614-
# shellcheck disable=SC2206
1615-
local flag_array=($flags)
1616-
full_command+=("${flag_array[@]}")
1629+
fi
1630+
;;
1631+
aider)
1632+
if [[ -n "$flags" ]]; then
1633+
# shellcheck disable=SC2206
1634+
local flag_array=($flags)
1635+
full_command+=("${flag_array[@]}")
1636+
fi
1637+
1638+
# If prompt provided and in Ghostty, inject it in background
1639+
if (( ${#command_args[@]} )); then
1640+
if is_ghostty; then
1641+
local prompt_joined
1642+
prompt_joined=$(printf '%s ' "${command_args[@]}"); prompt_joined=${prompt_joined%% }
1643+
inject_prompt_background "$prompt_joined" "aider" 4
1644+
else
1645+
print_error "aider with prompts requires Ghostty terminal for interactive mode"
1646+
print_info "Either run in Ghostty or launch aider without a prompt"
1647+
exit 1
16171648
fi
16181649
fi
16191650
;;

test.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ test_command_flags() {
153153
"opencode:" # no extra flags
154154
"qwen:--yolo"
155155
"kimi:--yolo"
156+
"aider:--yes-always --no-auto-commit"
156157
"unknown-tool:--yolo"
157158
)
158159

@@ -223,6 +224,45 @@ EOF
223224
rm -f "$test_script"
224225
}
225226

227+
# Test 5d: Aider gets prompt via AppleScript injection
228+
test_aider_prompt_injection() {
229+
print_test_header "Test 5d: Aider Prompt Injection"
230+
run_test
231+
232+
# Create a dummy aider that just echoes its arguments
233+
local test_script="/tmp/aider"
234+
cat > "$test_script" << 'EOF'
235+
#!/bin/bash
236+
echo "$@"
237+
EOF
238+
chmod +x "$test_script"
239+
240+
# Run yolo aider with a positional prompt
241+
local output
242+
if output=$(PATH="/tmp:$PATH" run_with_timeout "$YOLO_TEST_TIMEOUT" "$YOLO_CMD" aider "implement feature" 2>&1); then
243+
if echo "$output" | grep -F -q "--yes-always" && echo "$output" | grep -F -q "implement feature"; then
244+
print_pass "yolo aider adds --yes-always and passes prompt"
245+
else
246+
print_fail "yolo aider should add --yes-always and pass prompt (got: $output)"
247+
fi
248+
else
249+
print_info "Skipping aider prompt test (command not executed)"
250+
fi
251+
252+
# Test with worktree flag
253+
if output=$(PATH="/tmp:$PATH" run_with_timeout "$YOLO_TEST_TIMEOUT" "$YOLO_CMD" -w aider "implement feature" 2>&1); then
254+
if echo "$output" | grep -F -q "--yes-always" && echo "$output" | grep -F -q "implement feature"; then
255+
print_pass "yolo -w aider adds --yes-always and passes prompt"
256+
else
257+
print_fail "yolo -w aider should add --yes-always and pass prompt (got: $output)"
258+
fi
259+
else
260+
print_info "Skipping aider worktree test (command not executed)"
261+
fi
262+
263+
rm -f "$test_script"
264+
}
265+
226266
# Test 5c: Kimi gets --command when prompt is provided (single-agent)
227267
test_kimi_command_with_prompt() {
228268
print_test_header "Test 5c: Kimi --command Added With Prompt"
@@ -439,6 +479,7 @@ main() {
439479
test_qwen_interactive_with_prompt
440480
test_kimi_command_with_prompt
441481
test_kimi_cli_detection
482+
test_aider_prompt_injection
442483
test_worktree_creation
443484
test_worktree_no_git_error
444485
test_argument_preservation

0 commit comments

Comments
 (0)