# opencode-command.plugin.zsh _whatcmd_project_root() { emulate -L zsh local dir=$PWD local marker local -a markers=( .git .hg Cargo.toml go.mod pyproject.toml package.json dune-project CMakeLists.txt meson.build Makefile build.zig pom.xml build.gradle build.gradle.kts WORKSPACE WORKSPACE.bazel MODULE.bazel flake.nix mix.exs rebar.config cabal.project stack.yaml Gemfile composer.json AGENTS.md ) while true; do for marker in $markers; do if [[ -e "$dir/$marker" ]]; then REPLY=$dir return 0 fi done [[ $dir == / ]] && break dir=${dir:h} done REPLY='' return 1 } # # Describe the local environment to the command-generating agent. # _whatcmd_environment() { emulate -L zsh local os='unknown' local userland='unknown' local package_manager='unknown' local privilege='none' # # Distribution / OS. # if [[ -r /etc/os-release ]]; then local NAME='' PRETTY_NAME='' ID='' VERSION_ID='' . /etc/os-release os="${PRETTY_NAME:-${NAME:-${ID:-unknown}}}" elif [[ $OSTYPE == darwin* ]]; then os="macOS $(command sw_vers -productVersion 2>/dev/null)" else os="$(command uname -s 2>/dev/null)" fi # # Roughly identify the command-line userland. # if command ls --version >/dev/null 2>&1; then userland="$(command ls --version 2>/dev/null | head -n1)" elif (( $+commands[busybox] )); then userland="$(command busybox 2>&1 | head -n1)" elif [[ $OSTYPE == darwin* || $OSTYPE == *bsd* ]]; then userland='BSD-style userland' fi # # Prefer the native package manager. # local pm for pm in emerge dnf apt apt-get pacman zypper apk brew; do if (( $+commands[$pm] )); then package_manager=$pm break fi done # # This actually matters quite a bit for generated administrative # commands. # if (( $+commands[doas] )); then privilege=doas elif (( $+commands[sudo] )); then privilege=sudo fi print -r -- "OS: $os" print -r -- "Kernel: $(command uname -sr 2>/dev/null)" print -r -- "Architecture: $(command uname -m 2>/dev/null)" print -r -- "Shell: zsh $ZSH_VERSION" print -r -- "Userland: $userland" print -r -- "Package manager: $package_manager" print -r -- "Privilege command: $privilege" print -r -- "Current Id: $(id)" print -r -- "Working directory: $PWD" } # # Public interface. # # Examples: # # whatcmd find all files larger than 1GB # $(whatcmd get url of git origin) # whatcmd install imagemagick | wl-copy # whatcmd() { emulate -L zsh if (( $# == 0 )); then print -u2 -r -- 'usage: whatcmd ' return 2 fi local request="$*" local environment="$(_whatcmd_environment)" local prompt prompt=$'The command will be run in this environment:\n' prompt+="$environment" prompt+=$'\n\n' prompt+="Request: $request" local project_root='' local permissions if _whatcmd_project_root; then project_root=$REPLY permissions=' "*": "deny", "read": { "*": "allow", "*.env": "deny", "*.env.*": "deny", "*.env.example": "allow" }, "glob": "allow", "grep": "allow", "external_directory": "deny" ' else permissions=' "*": "deny" ' fi local opencode_config=' { "agent": { "whatcmd": { "description": "Generate exactly one shell command for a requested operation", "mode": "primary", "permission": { '"$permissions"' }, "prompt": "You are a shell-command generator, not a conversational assistant.\n\nThe user describes an operation they want to perform. Your job is to output the zsh command they should run to perform that operation.\n\nNEVER perform the requested operation yourself.\nNEVER return the result of the requested operation.\nNEVER answer the request directly.\nALWAYS return a command that the user can run to obtain the requested result or perform the requested action.\n\nFor example:\n- Request: get the URL of the git origin\n Output: git remote get-url origin\n\n- Request: find all executable files under the current directory\n Output: find . -type f -executable\n\n- Request: show processes listening on TCP ports\n Output: ss -ltnp\n\nYou may use read, glob, and grep ONLY when local project-specific information is necessary to determine which command should be run. Examples include:\n- compile this project\n- run this project tests\n- start this application\n- build the current package\n\nFor generic shell questions, do not inspect the filesystem.\n\nWhen you inspect a project, inspection is only evidence for choosing the command. Do not return information discovered during inspection as the answer.\n\nYou must never execute shell commands, modify files, access the network, invoke other agents, or perform the requested operation.\n\nYour final response must contain EXACTLY the shell command and nothing else.\nNo markdown.\nNo explanation.\nNo commentary.\nNo prefix or suffix." } } } ' OPENCODE_CONFIG_CONTENT="$opencode_config" \ command opencode run \ --agent whatcmd \ --format json \ --thinking \ "$prompt" | _whatcmd_demux local -a pipeline_status=("${pipestatus[@]}") # Preserve an OpenCode failure rather than just returning the # demuxer's status. (( pipeline_status[1] != 0 )) && return $pipeline_status[1] return $pipeline_status[2] } _whatcmd_demux() { emulate -L zsh local line type text tool target local final='' while IFS= read -r line; do type=$(jq -r '.type // empty' <<< "$line") || continue case "$type" in text) # Don't emit yet. The last completed text part is the # command we care about. final=$(jq -r '.part.text // empty' <<< "$line") ;; reasoning) text=$( jq -r ' (.part.text // "") | gsub("[\r\n\t]+"; " ") | gsub(" +"; " ") ' <<< "$line" ) [[ -n $text ]] && print -u2 -r -- "⬤ $text" ;; tool_use) tool=$(jq -r '.part.tool // "tool"' <<< "$line") case "$tool" in read) target=$( jq -r ' .part.state.input.filePath // .part.state.input.path // "" ' <<< "$line" ) print -u2 -r -- "󰈙 Reading ${target:t}" ;; glob) print -u2 -r -- "󰱼 Looking through project files" ;; grep) print -u2 -r -- "󰱼 Searching project" ;; *) print -u2 -r -- "⚙ Using $tool" ;; esac ;; error) text=$( jq -r ' .error.data.message // .error.message // .error.name // "OpenCode error" ' <<< "$line" ) print -u2 -r -- "✗ $text" ;; esac done [[ -n $final ]] && print -r -- "$final" } # # Minibuffer highlighting # # This replaces all region_highlight entries immediately before every # minibuffer redraw. That prevents zsh-syntax-highlighting and # fast-syntax-highlighting from leaking stale ranges into the recursive # editor. # _opencode_minibuffer_pre_redraw() { emulate -L zsh region_highlight=( "P${_opencode_prompt_start} $((_opencode_prompt_start + 2)) fg=yellow,bold" "P$((_opencode_prompt_start + 2)) ${_opencode_prompt_end} fg=magenta,bold" ) } # # Customized read-from-minibuffer. # # - colored OpenCode prompt # - plain editable text # - no shell syntax highlighting # - preserves/restores original editing buffer # _opencode_minibuffer() { emulate -L zsh local readprompt="$1" integer stat=1 integer savelim=$UNDO_LIMIT_NO integer changeno=$UNDO_CHANGE_NO { () { local pretext="$PREDISPLAY$LBUFFER$RBUFFER$POSTDISPLAY " local +h LBUFFER= local +h RBUFFER= local +h PREDISPLAY="$pretext$readprompt" local +h POSTDISPLAY= # # These are special ZLE parameters, so localize them for the # recursive editor. # local +h -a region_highlight local +h -a zle_highlight # # Don't apply any of ZLE's normal visual highlighting to the # natural-language text. # zle_highlight=( 'default:none' 'region:none' 'special:none' 'suffix:none' 'isearch:none' 'paste:none' ) # # These are dynamically scoped, so # _opencode_minibuffer_pre_redraw can see them. # integer _opencode_prompt_start=${#pretext} integer _opencode_prompt_end=${#PREDISPLAY} # # Temporarily replace zle-line-pre-redraw. # # This is stronger than trying to configure individual syntax # highlighters: regardless of what they put in region_highlight, # our hook runs immediately before display and replaces it with # exactly the one range we want. # integer had_pre_redraw=0 if zle -l zle-line-pre-redraw >/dev/null 2>&1; then zle -A zle-line-pre-redraw _opencode_saved_pre_redraw had_pre_redraw=1 fi { zle -N \ zle-line-pre-redraw \ _opencode_minibuffer_pre_redraw # # Clear any stale syntax-highlighting ranges before the # recursive editor even starts. # region_highlight=( "P${_opencode_prompt_start} ${_opencode_prompt_end} fg=magenta,bold" ) local NUMERIC unset NUMERIC zle split-undo UNDO_LIMIT_NO=$UNDO_CHANGE_NO zle recursive-edit -K main stat=$? (( stat )) || REPLY=$BUFFER } always { # # Restore the user's normal redraw widget, which in turn # restores zsh-syntax-highlighting/F-Sy-H behavior at the # normal shell prompt. # if (( had_pre_redraw )); then zle -A \ _opencode_saved_pre_redraw \ zle-line-pre-redraw zle -D _opencode_saved_pre_redraw else zle -D zle-line-pre-redraw fi } } } always { # # Remove the recursive editor's changes from the original command # line's undo history. # zle undo $changeno UNDO_LIMIT_NO=$savelim } return $stat } _whatcmd_autosuggest_pause() { emulate -L zsh # # Remember whether autosuggestions were already disabled before us. # _whatcmd_autosuggest_was_disabled=${+_ZSH_AUTOSUGGEST_DISABLED} if (( ${+widgets[autosuggest-disable]} )); then zle autosuggest-disable fi POSTDISPLAY= } _whatcmd_autosuggest_resume() { emulate -L zsh POSTDISPLAY= # # Don't enable it if the user already had it disabled before invoking # whatcmd. # if (( !_whatcmd_autosuggest_was_disabled )) && (( ${+widgets[autosuggest-enable]} )); then zle autosuggest-enable fi unset _whatcmd_autosuggest_was_disabled } # # Ctrl-? widget # _opencode_command() { emulate -L zsh local request # # Save the outer ZLE state. History navigation inside recursive-edit # changes HISTNO, and that can otherwise leak back into the real prompt. # local saved_buffer=$BUFFER integer saved_cursor=$CURSOR integer saved_histno=$HISTNO _whatcmd_autosuggest_pause { # # Give the natural-language minibuffer its own history. # () { local history_file="${XDG_STATE_HOME:-$HOME/.local/state}/opencode-command/history" mkdir -p -- "${history_file:h}" || return 1 # # Push normal shell history and load our OpenCode history. # # -a restores the previous history automatically when this # anonymous function returns. # fc -p -a "$history_file" 1000 1000 _opencode_minibuffer " ⬤ What command " || return request=$REPLY [[ -z "${request//[[:space:]]/}" ]] && return 1 # # Store the natural-language request in OpenCode history. # print -s -- "$request" } local minibuffer_ec=$? # # The anonymous function has exited, so fc -p -a has restored the # normal shell history. Restore the outer editor's history position # and command line. # # Assigning HISTNO can itself load a history entry into BUFFER, so # restore HISTNO first and BUFFER afterward. # HISTNO=$saved_histno BUFFER=$saved_buffer CURSOR=$saved_cursor (( minibuffer_ec != 0 )) && return $minibuffer_ec unsetopt MONITOR local tmpdir tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/whatcmd.XXXXXX") || return 1 local result_file="$tmpdir/result" local status_file="$tmpdir/status" local stderr_fifo="$tmpdir/stderr" mkfifo "$stderr_fifo" || { rm -rf -- "$tmpdir" return 1 } # # Open the FIFO read/write in the parent so opening either end # never blocks waiting for the other side. # local fifo_fd exec {fifo_fd}<>"$stderr_fifo" ( whatcmd "$request" >| "$result_file" 2>"$stderr_fifo" print -r -- "$?" >| "$status_file" ) & local -a spinner=( '⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏' ) local i=1 local line='' local message='⬤ Determining command ...' while [[ ! -e "$status_file" ]]; do # # Grab a stderr line if one is available, but don't block # longer than one animation frame. # if read -r -t 0.1 -u $fifo_fd line; then [[ -n $line ]] && message=$line fi POSTDISPLAY=$'\n'"${spinner[i]} $message" local pos=$(( ${#PREDISPLAY} + ${#BUFFER} + 1 )) local pos_end=$(( ${#PREDISPLAY} + ${#BUFFER} + ${#POSTDISPLAY} )) region_highlight+=( "P${pos} $(( pos + 1 )) fg=red,bold memo=whatcmd-spinner" "P$((pos + 1)) $(( pos + 4 )) fg=yellow,bold memo=whatcmd-spinner" # "P$(( pos + 4 )) ${pos_end} fg=cyan,bold memo=whatcmd-spinner" ) zle -R (( i = i % ${#spinner} + 1 )) done local ec read -r ec < "$status_file" local result result="$(<"$result_file")" exec {fifo_fd}>&- rm -rf -- "$tmpdir" zle -R if (( ec != 0 )); then zle -I print -u2 -r -- "✗ opencode failed: exit status $ec" return $ec fi if [[ -z "${result//[[:space:]]/}" ]]; then zle -I print -u2 -r -- '✗ opencode returned nothing' return 1 fi } always { _whatcmd_autosuggest_resume } # # Replace the original command line with the generated command, # but don't execute it. # BUFFER=$result CURSOR=${#BUFFER} } zle -N _opencode_command # Ctrl-? as emitted by your terminal (0x1f / ^_). bindkey $'\x1f' _opencode_command