Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion shell/cmdhelp.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ _cmdhelp_widget() {
{
print -rn -- "$saved_buffer" > "$task_dir/input"
zle -I
if cmdhelp ui --mode "$1" --buffer-file "$task_dir/input" --output-file "$task_dir/result" < /dev/tty > /dev/tty; then
# Bun on macOS does not receive stdin events through /dev/tty.
# Zsh's TTY names the concrete device (e.g. /dev/ttys001).
if cmdhelp ui --mode "$1" --buffer-file "$task_dir/input" --output-file "$task_dir/result" < "$TTY" > "$TTY"; then
if [[ -s "$task_dir/result" ]]; then
selected=$(<"$task_dir/result")
BUFFER=$selected
Expand Down
5 changes: 5 additions & 0 deletions test/installation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { join } from 'node:path';
import { fileURLToPath } from 'node:url';

const root = fileURLToPath(new URL('../', import.meta.url));
test('zsh widgets accept keyboard input through Bun in a real terminal', () => {
const output = execFileSync('zsh', [join(root, 'test/interactive.zsh')], { encoding: 'utf8', timeout: 20_000 });
assert.match(output, /Interactive checks passed/);
}, 25_000);

test('CLI and zsh initialization work from a relocated installation with quotes/spaces', async () => {
const dir = await mkdtemp(join(tmpdir(), "cmdhelp user's app-"));
try {
Expand Down
57 changes: 57 additions & 0 deletions test/interactive.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/bin/zsh
set -eu
zmodload zsh/zpty
zmodload zsh/zselect

root=${0:A:h:h}
fixture=$(mktemp -d)
export XDG_CONFIG_HOME=$fixture CMDHELP_PI_PROFILE=$fixture
export CMDHELP_PROVIDER=cmdhelp-test-invalid CMDHELP_MODEL=cmdhelp-test-invalid
export TERM=xterm-256color

# Exercise real ZLE and Bun input, without credentials or model requests.
expect_output() {
local expected=$1 chunk transcript='' attempt
for attempt in {1..500}; do
while zpty -r cmdhelp_test chunk; do
transcript+=$chunk
[[ $transcript == *"$expected"* ]] && return 0
done
zselect -t 1 || true
done
print -ru2 -- "Timed out waiting for: $expected"
print -ru2 -- "$transcript"
return 1
}

{
zpty -b cmdhelp_test zsh -f
zpty -w cmdhelp_test "PS1='CMDHELP_READY> '; source ${(q)root}/shell/cmdhelp.zsh"
expect_output 'CMDHELP_READY> '
zpty -w -n cmdhelp_test $'\x18\x07'
expect_output 'Enter: send'
zpty -w -n cmdhelp_test 'keyboard-probe'
expect_output '> keyboard-probe'
zpty -w -n cmdhelp_test $'\r'
expect_output 'Tab: follow-up'
zpty -w -n cmdhelp_test $'\t'
expect_output 'Enter: send'
zpty -w -n cmdhelp_test 'followup-probe'
expect_output '> followup-probe'
zpty -w -n cmdhelp_test $'\e'
expect_output 'CMDHELP_READY> '
# Explain auto-submits the existing buffer, then accepts Tab and Escape.
zpty -w -n cmdhelp_test $'echo preserved\x18\x05'
expect_output 'Tab: follow-up'
zpty -w -n cmdhelp_test $'\t'
expect_output 'Enter: send'
zpty -w -n cmdhelp_test 'explain-probe'
expect_output '> explain-probe'
zpty -w -n cmdhelp_test $'\e'
expect_output 'echo preserved'
zpty -w -n cmdhelp_test $'\x15exit\r'
print 'Interactive checks passed: text, Enter, Tab, Escape, and buffer restoration in both widgets.'
} always {
zpty -d cmdhelp_test 2>/dev/null || true
command rm -rf -- "$fixture"
}
Loading