Created
October 23, 2025 17:54
-
-
Save bendytree/ca27db29e8ccd5560533928cb018caa8 to your computer and use it in GitHub Desktop.
isuckatbash: Use GPT to generate or fix zsh commands from your prompt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ----------------------------------------------------------------------------- | |
| # 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 |
Author
bendytree
commented
Oct 23, 2025

Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment