Background
hey confirms the suggested command in a TUI prompt and then Command::new($SHELL).arg("-c").arg(command) runs it as a child process (src/main.rs:336-345). This means:
cd, export, source cannot affect the parent shell — currently surfaced as Warn with "affects only this subshell" (correct, but a workaround).
- The user can't trivially edit the suggestion before running it (the confirm flow is yes/no/edit-in-place, not "hand it to my shell").
A common pattern in shell_gpt, aichat, and friends is a shell function that captures the suggested command and pushes it to the readline buffer (print -z in zsh, commandline in fish, READLINE_LINE in bash). The user sees the command on their prompt, can edit it, and runs it themselves.
Constraint
hey init <shell> is taken — it generates clap_complete scripts (src/completions.rs:11-20, dispatched at src/main.rs:62-65). Whatever this feature is called must not collide.
Shell-buffer mechanics (worth getting right up front)
- zsh:
print -z -- "$cmd" pushes onto the editor buffer stack; the line appears at the next prompt with the cursor at end. Works from a regular function. Fine.
- fish:
commandline -- "$cmd" replaces the current command line inline. Works from a regular function. Fine.
- bash:
READLINE_LINE is only writable from a function invoked via bind -x '"\C-g": _hey_edit' (i.e. a readline keybinding callback). A function called from the prompt cannot prefill the next buffer. Any bash story here requires the user to bind a key, or we accept "bash gets a less nice UX" (e.g. just copy to clipboard / print, no buffer push).
This is the load-bearing constraint — the bash limitation shapes the whole design.
Proposed shape
Two pieces:
- A clean-stdout mode in
hey. Today --dry-run prints a decorated TUI block via print_command_block (src/main.rs:238-246) — not capturable. Make --dry-run emit just the bare command on stdout when stdout is not a TTY (we already detect this at src/main.rs:121). No new flag needed.
- A
hey init <shell> extension OR a sibling subcommand that prints a shell function the user sources. The function calls hey --dry-run "$@" | <buffer-push>.
Open question: extend hey init to also emit the helper, or add a sibling like hey shell-init <shell>? The former is one install command but mixes completions + helper; the latter is symmetric but adds a subcommand. Leaning toward extending hey init since both are "shell glue" and the snippet is small.
Sketch
zsh:
hey-edit() {
local out
out="$(command hey --dry-run "$@")" || return
print -z -- "$out"
}
fish:
function hey-edit
set -l out (command hey --dry-run $argv); or return
commandline -- $out
end
bash (requires keybinding, not a plain function):
_hey_edit() {
local out
out="$(command hey --dry-run "${READLINE_LINE}")" || return
READLINE_LINE="$out"
READLINE_POINT=${#READLINE_LINE}
}
bind -x '"\C-g": _hey_edit' # type prompt, hit Ctrl-G
The bash flow is genuinely different (you type the prompt onto the command line first, then hit a binding). If that's too much UX divergence, bash can fall back to clipboard.
Why not always use this?
- Today
hey ls docker containers is one keystroke. Buffer insertion adds Enter.
- Users who want pure execution shouldn't be forced to install a shell function.
Buffer insertion stays opt-in alongside the existing flow.
Out of scope
- PowerShell / Windows.
- Streaming the suggestion as it generates.
- Replacing the existing
confirm() prompt.
Decision points
- Reuse
--dry-run (+ make it TTY-aware) vs. add a dedicated --print flag?
- Extend
hey init <shell> to emit completions + helper, or add a separate hey shell-init <shell>?
- Bash:
bind -x snippet, clipboard fallback, or skip bash for v1?
Background
heyconfirms the suggested command in a TUI prompt and thenCommand::new($SHELL).arg("-c").arg(command)runs it as a child process (src/main.rs:336-345). This means:cd,export,sourcecannot affect the parent shell — currently surfaced asWarnwith"affects only this subshell"(correct, but a workaround).A common pattern in
shell_gpt,aichat, and friends is a shell function that captures the suggested command and pushes it to the readline buffer (print -zin zsh,commandlinein fish,READLINE_LINEin bash). The user sees the command on their prompt, can edit it, and runs it themselves.Constraint
hey init <shell>is taken — it generatesclap_completescripts (src/completions.rs:11-20, dispatched atsrc/main.rs:62-65). Whatever this feature is called must not collide.Shell-buffer mechanics (worth getting right up front)
print -z -- "$cmd"pushes onto the editor buffer stack; the line appears at the next prompt with the cursor at end. Works from a regular function. Fine.commandline -- "$cmd"replaces the current command line inline. Works from a regular function. Fine.READLINE_LINEis only writable from a function invoked viabind -x '"\C-g": _hey_edit'(i.e. a readline keybinding callback). A function called from the prompt cannot prefill the next buffer. Any bash story here requires the user to bind a key, or we accept "bash gets a less nice UX" (e.g. just copy to clipboard / print, no buffer push).This is the load-bearing constraint — the bash limitation shapes the whole design.
Proposed shape
Two pieces:
hey. Today--dry-runprints a decorated TUI block viaprint_command_block(src/main.rs:238-246) — not capturable. Make--dry-runemit just the bare command on stdout whenstdoutis not a TTY (we already detect this atsrc/main.rs:121). No new flag needed.hey init <shell>extension OR a sibling subcommand that prints a shell function the user sources. The function callshey --dry-run "$@" | <buffer-push>.Open question: extend
hey initto also emit the helper, or add a sibling likehey shell-init <shell>? The former is one install command but mixes completions + helper; the latter is symmetric but adds a subcommand. Leaning toward extendinghey initsince both are "shell glue" and the snippet is small.Sketch
zsh:
fish:
bash (requires keybinding, not a plain function):
The bash flow is genuinely different (you type the prompt onto the command line first, then hit a binding). If that's too much UX divergence, bash can fall back to clipboard.
Why not always use this?
hey ls docker containersis one keystroke. Buffer insertion adds Enter.Buffer insertion stays opt-in alongside the existing flow.
Out of scope
confirm()prompt.Decision points
--dry-run(+ make it TTY-aware) vs. add a dedicated--printflag?hey init <shell>to emit completions + helper, or add a separatehey shell-init <shell>?bind -xsnippet, clipboard fallback, or skip bash for v1?