|
# ----------------------------------------------------------------------------- |
|
# isuckatbash.zsh — press Esc-; to ask GPT for shell help |
|
# |
|
# Description: |
|
# Sends your current zsh command line (BUFFER) to OpenAI's API and replaces it |
|
# with a suggested command plus a short explanation. |
|
# |
|
# Usage: |
|
# 1. Add this snippet to ~/.zshrc |
|
# 2. Set your OpenAI key env OPENAI_API_KEY |
|
# 3. Type a goal or partial command, then press Esc-; (macos) |
|
# |
|
# Example: |
|
# $ list jpg files recurs ← then press [ESC]+[;] |
|
# thinking... |
|
# List all .jpg files recursively from current directory. |
|
# > find . -type f -iname '*.jpeg' # also matches .jpeg |
|
# > find . -type f \( -iname '*.jpg' -o -iname '*.jpeg' \) # both .jpg and .jpeg |
|
# $ find . -type f -iname "*.jpg" ← ready to edit or run |
|
# |
|
# ----------------------------------------------------------------------------- |
|
isuckatbash() { |
|
typeset -g _isuckatbash_next="${BUFFER}" |
|
print -sr -- "$BUFFER" |
|
zle send-break |
|
} |
|
zle -N isuckatbash |
|
bindkey '^[;' isuckatbash # esc-; |
|
|
|
autoload -Uz add-zle-hook-widget |
|
|
|
_isuckatbash_line_init() { |
|
if [[ -n $_isuckatbash_next ]]; then |
|
BUFFER="" |
|
CURSOR=${#BUFFER} |
|
|
|
USER_INPUT="${_isuckatbash_next}" node - <<'NODESCRIPT' |
|
console.log = ((_log) => (...args) => { |
|
_log('\x1b[36m', ...args, '\x1b[0m'); |
|
})(console.log); |
|
process.stdout.write('\x1b[2K\r'); // next line |
|
|
|
if (!process.env.USER_INPUT) { |
|
console.log('No user input found.'); |
|
process.exit(0); |
|
} |
|
|
|
const os = require('os'); |
|
const fs = require('fs'); |
|
const controller = new AbortController(); |
|
const timeout = setTimeout(() => controller.abort(), 30000); |
|
|
|
(async () => { |
|
const sysExample = `{"command":"find . -iname '*foo*'","warning":"","details":"List files/dirs recursively.\n> find . -type f -iname '*foo*' # only files\n> find . -type d -iname '*foo*' # only dirs"}`; |
|
const sys1 = "Create a one-line zsh command to accomplish the goal stated below. " + |
|
"Return a JSON object like " + sysExample + ". " + |
|
`"command" is the one-line zsh command; "warning" is optional and will be shown in red - use it for deletes, etc (and suggest a dry run version if possible). "details" is a brief explanation plus related options (one per line) as needed.\n\nIf the user provided a command, then start details by confirming if's a valid command by saying "Perfect..." or invalid "Close...". Then explain what it does and give related options and provide the corrected command as the result.`; |
|
const sys2 = JSON.stringify({ |
|
shell: process.env.SHELL, |
|
os: process.platform, |
|
osRelease: os.release(), |
|
osArch: os.arch(), |
|
homeDir: os.homedir(), |
|
hostname: os.hostname(), |
|
pwd: process.cwd(), |
|
nodeVersion: process.version, |
|
}); |
|
|
|
try { |
|
process.stdout.write('\x1b[2mthinking...\x1b[0m\n'); |
|
const res = await fetch('https://api.openai.com/v1/chat/completions', { |
|
signal: controller.signal, |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` }, |
|
body: JSON.stringify({ |
|
model: 'gpt-4.1', |
|
messages: [ |
|
{ role: 'system', content: sys1 }, |
|
{ role: 'system', content: 'Keep it simple if possible. For advanced commands, consider using node -e.\n\n' + sys2 }, |
|
{ role: 'user', content: process.env.USER_INPUT } |
|
] |
|
}), |
|
}); |
|
|
|
const j = await res.json(); |
|
const payload = JSON.parse(j?.choices?.[0]?.message?.content ?? ""); |
|
if (payload.details) console.log(String(payload.details + "\n").trim()); |
|
if (payload.warning) process.stdout.write('\x1b[31m'+payload.warning+'\x1b[0m\n'); |
|
if (payload.command) fs.writeFileSync('/tmp/_isuckatbash_cmd.txt', payload.command); |
|
} catch (e) { |
|
if (e.name === 'AbortError') { |
|
console.log("API request timed out."); |
|
} else { |
|
console.log(String(e?.message || e)); |
|
} |
|
} finally { |
|
clearTimeout(timeout); |
|
} |
|
})(); |
|
NODESCRIPT |
|
|
|
BUFFER="$(< /tmp/_isuckatbash_cmd.txt)" |
|
CURSOR=${#BUFFER} |
|
unset _isuckatbash_next |
|
zle reset-prompt |
|
zle -R |
|
fi |
|
} |
|
|
|
add-zle-hook-widget line-init _isuckatbash_line_init |