1# SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2#
3# SPDX-License-Identifier: Unlicense
4
5# Fallback to the configured agent command (Crush by default) when a command is not found.
6
7# Configure the prefix that marks commands for routing to the agent.
8typeset -g __AGENTSH_PREFIX_CHAR
9: "${__AGENTSH_PREFIX_CHAR:="✨"}"
10
11typeset -g __AGENTSH_PREFIX
12: "${__AGENTSH_PREFIX:="${__AGENTSH_PREFIX_CHAR} "}"
13
14typeset -ga __AGENTSH_RUNNER
15if ((${#__AGENTSH_RUNNER[@]} == 0)); then
16 __AGENTSH_RUNNER=("crush" "run" "--quiet")
17fi
18
19typeset -g __AGENTSH_PREFIX_ACTIVE=0
20typeset -g __AGENTSH_WIDGETS_INSTALLED=0
21typeset -g __AGENTSH_HAS_PREV_LINE_INIT=0
22typeset -g __AGENTSH_HAS_PREV_LINE_PRE_REDRAW=0
23typeset -g __AGENTSH_HAS_PREV_LINE_FINISH=0
24typeset -gA __AGENTSH_GUARD_WIDGET_ALIASES=()
25
26# Preserve any previously defined handler so we can delegate if needed.
27if (($ + functions[command_not_found_handler])); then
28 functions[__agentsh_original_command_not_found_handler]=$functions[command_not_found_handler]
29fi
30
31command_not_found_handler() {
32 emulate -L zsh
33
34 local missing_command="$1"
35 local -a cmd_with_args=("$@")
36
37 shift
38 local -a remaining_args=("$@")
39
40 # Nothing to do without a command name.
41 if [[ -z "$missing_command" ]]; then
42 if (($ + functions[__agentsh_original_command_not_found_handler])); then
43 __agentsh_original_command_not_found_handler "${cmd_with_args[@]}"
44 return $?
45 fi
46 return 127
47 fi
48
49 local prefix_char="${__AGENTSH_PREFIX_CHAR:-✨}"
50
51 local handled=false
52 local -a effective_cmd=()
53
54 if [[ "$missing_command" == "$prefix_char" ]]; then
55 handled=true
56 effective_cmd=("${remaining_args[@]}")
57 elif [[ "$missing_command" == ${prefix_char}* ]]; then
58 handled=true
59 local stripped="${missing_command#"$prefix_char"}"
60 if [[ -n "$stripped" ]]; then
61 effective_cmd=("$stripped" "${remaining_args[@]}")
62 else
63 effective_cmd=("${remaining_args[@]}")
64 fi
65 fi
66
67 if [[ "$handled" != true ]]; then
68 if (($ + functions[__agentsh_original_command_not_found_handler])); then
69 __agentsh_original_command_not_found_handler "${cmd_with_args[@]}"
70 return $?
71 fi
72 print -u2 "zsh: command not found: ${missing_command}"
73 return 127
74 fi
75
76 if ((${#effective_cmd[@]} == 0)); then
77 print -u2 "agentsh: nothing to run after '${prefix_char}'."
78 return 127
79 fi
80
81 local runner_exe="${__AGENTSH_RUNNER[1]:-}"
82 if [[ -z "$runner_exe" ]]; then
83 print -u2 "agentsh: no runner configured; set __AGENTSH_RUNNER before loading the plugin."
84 return 127
85 fi
86
87 if ! command -v "$runner_exe" >/dev/null 2>&1; then
88 if (($ + functions[__agentsh_original_command_not_found_handler])); then
89 __agentsh_original_command_not_found_handler "${cmd_with_args[@]}"
90 return $?
91 fi
92 print -u2 "agentsh: '${runner_exe}' command not found; unable to handle '${effective_cmd[1]}'."
93 return 127
94 fi
95
96 local full_cmd
97 full_cmd="${(j: :)effective_cmd}"
98
99 "${__AGENTSH_RUNNER[@]}" "$full_cmd"
100 return $?
101}
102
103__agentsh_toggle_prefix() {
104 emulate -L zsh
105
106 local prefix="${__AGENTSH_PREFIX:-${__AGENTSH_PREFIX_CHAR} }"
107 local prefix_len=${#prefix}
108
109 if [[ "$BUFFER" == "$prefix"* ]]; then
110 BUFFER="${BUFFER#"$prefix"}"
111 if ((CURSOR > prefix_len)); then
112 CURSOR=$((CURSOR - prefix_len))
113 else
114 CURSOR=0
115 fi
116 __AGENTSH_PREFIX_ACTIVE=0
117 else
118 BUFFER="${prefix}${BUFFER}"
119 CURSOR=$((CURSOR + prefix_len))
120 __AGENTSH_PREFIX_ACTIVE=1
121 fi
122}
123
124__agentsh_line_init() {
125 emulate -L zsh
126
127 # Add prefix at the start of a new line if prefix mode is active
128 if ((__AGENTSH_PREFIX_ACTIVE)); then
129 local prefix="${__AGENTSH_PREFIX:-${__AGENTSH_PREFIX_CHAR} }"
130 BUFFER="${prefix}"
131 CURSOR=${#prefix}
132 fi
133
134 if ((__AGENTSH_HAS_PREV_LINE_INIT)); then
135 zle __agentsh_prev_line_init
136 fi
137}
138
139__agentsh_line_pre_redraw() {
140 emulate -L zsh
141
142 if ((__AGENTSH_PREFIX_ACTIVE)); then
143 local prefix="${__AGENTSH_PREFIX:-${__AGENTSH_PREFIX_CHAR} }"
144 local prefix_len=${#prefix}
145
146 if ((CURSOR < prefix_len)); then
147 CURSOR=$prefix_len
148 fi
149
150 local buffer_len=${#BUFFER}
151 if ((CURSOR > buffer_len)); then
152 CURSOR=$buffer_len
153 fi
154 fi
155
156 if ((__AGENTSH_HAS_PREV_LINE_PRE_REDRAW)); then
157 zle __agentsh_prev_line_pre_redraw
158 fi
159}
160
161__agentsh_line_finish() {
162 emulate -L zsh
163
164 if ((__AGENTSH_HAS_PREV_LINE_FINISH)); then
165 zle __agentsh_prev_line_finish
166 fi
167}
168
169__agentsh_guard_backward_action() {
170 emulate -L zsh
171
172 if ((!__AGENTSH_PREFIX_ACTIVE)); then
173 __agentsh_call_guarded_original
174 return
175 fi
176
177 local prefix="${__AGENTSH_PREFIX:-${__AGENTSH_PREFIX_CHAR} }"
178 local prefix_len=${#prefix}
179
180 if [[ "$BUFFER" == "$prefix"* ]] && ((CURSOR <= prefix_len)); then
181 zle beep 2>/dev/null
182 return
183 fi
184
185 __agentsh_call_guarded_original
186}
187
188__agentsh_call_guarded_original() {
189 emulate -L zsh
190
191 local alias="${__AGENTSH_GUARD_WIDGET_ALIASES[$WIDGET]-}"
192 if [[ -n "$alias" ]]; then
193 zle "$alias" 2>/dev/null
194 else
195 zle ".${WIDGET}" 2>/dev/null
196 fi
197}
198
199__agentsh_register_guard_widget() {
200 emulate -L zsh
201
202 local widget="$1"
203 local alias="__agentsh_prev_${widget//-/_}"
204
205 if zle -A "$widget" "$alias" 2>/dev/null; then
206 __AGENTSH_GUARD_WIDGET_ALIASES[$widget]="$alias"
207 else
208 __AGENTSH_GUARD_WIDGET_ALIASES[$widget]=""
209 fi
210
211 zle -N "$widget" __agentsh_guard_backward_action
212}
213
214if [[ -o interactive ]]; then
215 zle -N __agentsh_toggle_prefix
216
217 typeset -a __agentsh_keymaps=("emacs" "viins")
218 typeset keymap
219 for keymap in "${__agentsh_keymaps[@]}"; do
220 bindkey -M "$keymap" '^X' __agentsh_toggle_prefix 2>/dev/null
221 done
222 unset keymap __agentsh_keymaps
223
224 if ((!__AGENTSH_WIDGETS_INSTALLED)); then
225 if zle -A zle-line-init __agentsh_prev_line_init 2>/dev/null; then
226 __AGENTSH_HAS_PREV_LINE_INIT=1
227 fi
228 zle -N zle-line-init __agentsh_line_init
229
230 if zle -A zle-line-pre-redraw __agentsh_prev_line_pre_redraw 2>/dev/null; then
231 __AGENTSH_HAS_PREV_LINE_PRE_REDRAW=1
232 fi
233 zle -N zle-line-pre-redraw __agentsh_line_pre_redraw
234
235 if zle -A zle-line-finish __agentsh_prev_line_finish 2>/dev/null; then
236 __AGENTSH_HAS_PREV_LINE_FINISH=1
237 fi
238 zle -N zle-line-finish __agentsh_line_finish
239 __agentsh_register_guard_widget backward-delete-char
240 __agentsh_register_guard_widget backward-kill-word
241 __agentsh_register_guard_widget vi-backward-delete-char
242 __agentsh_register_guard_widget vi-backward-kill-word
243
244 __AGENTSH_WIDGETS_INSTALLED=1
245 fi
246fi