agentsh.zsh

  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