Last active
July 4, 2025 13:10
-
-
Save juanluisrto/15b224f6a08c8c6fc28b73c0922efea9 to your computer and use it in GitHub Desktop.
a chat command that sends your visible terminal state to GPT and preloads the reply as your next command.
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
#!/usr/bin/env zsh | |
# chat – ask OpenAI using the last visible terminal lines (curl + jq) | |
chat() { | |
local lines=30 # how many terminal lines to capture | |
local model="gpt-4.1-nano" # default model | |
local verbose=false | |
local -a query_parts=() | |
# -------------------- parse options -------------------- | |
while (( $# )); do | |
case "$1" in | |
-l|--lines) | |
[[ -n $2 && $2 != -* ]] || { echo "Option $1 needs a value" >&2; return 1; } | |
lines=$2; shift 2 ;; | |
-m|--model) | |
[[ -n $2 && $2 != -* ]] || { echo "Option $1 needs a value" >&2; return 1; } | |
model=$2; shift 2 ;; | |
-v|--verbose) | |
verbose=true; shift ;; | |
-h|--help) | |
cat <<'HELP' | |
chat — ask OpenAI for the next shell command | |
-l, --lines N number of terminal lines to send (default 30) | |
-m, --model M model name (default gpt-4.1-nano) | |
-v, --verbose print curl request, raw JSON response, and parsed command | |
-h, --help show this help | |
-- stop option parsing; everything after is query text | |
Examples: | |
chat git commit -m "initial" | |
chat --model gpt-4o-mini -v how to write a Makefile | |
chat how to use ls -l -- -R | |
HELP | |
return 0 ;; | |
--) # explicit end of options | |
shift; query_parts+=("$@"); break ;; | |
-*) | |
echo "Unknown option: $1" >&2; return 1 ;; | |
*) | |
query_parts+=("$1"); shift ;; | |
esac | |
done | |
(( ${#query_parts} )) || { echo "No query given" >&2; return 1; } | |
local query="${(j: :)query_parts}" | |
# ------------------------------------------------------- | |
[[ -z $OPENAI_API_KEY ]] && { echo "OPENAI_API_KEY not set" >&2; return 1; } | |
# --------------- capture terminal excerpt -------------- | |
if [[ -z $TMUX ]]; then | |
echo "Not inside tmux; can't capture pane output." >&2 | |
return 1 | |
fi | |
local pane | |
pane=$(tmux capture-pane -p -S -$((lines+1)) | sed '$d' | tail -n $lines) | |
# ------------------ build prompt ----------------------- | |
local prompt="You are a CLI assistant. Given the following terminal \ | |
session excerpt, reply with ONLY the next shell command—no explanations, \ | |
no markdown. | |
Terminal excerpt: | |
$pane | |
Request: $query" | |
local payload | |
payload=$(jq -n --arg model "$model" --arg p "$prompt" \ | |
'{model:$model, input:$p, temperature:0.3, background:false}') | |
# ------------------ headers ---------------------------- | |
local -a headers=( | |
-H "Content-Type: application/json" | |
-H "Authorization: Bearer $OPENAI_API_KEY" | |
) | |
[[ -n $OPENAI_PROJECT_ID ]] && headers+=(-H "OpenAI-Project: $OPENAI_PROJECT_ID") | |
[[ -n $OPENAI_ORG_ID ]] && headers+=(-H "OpenAI-Organization: $OPENAI_ORG_ID") | |
# ------------------ request ---------------------------- | |
local -a curl_cmd=(curl -s https://api.openai.com/v1/responses "${headers[@]}" -d "$payload") | |
local response | |
response=$("${curl_cmd[@]}") | |
# handle API-side errors early | |
if jq -e '.error' >/dev/null 2>&1 <<<"$response"; then | |
jq -r '.error.message' <<<"$response" >&2 | |
return 2 | |
fi | |
# ------------------ extract command -------------------- | |
local cmd | |
cmd=$(jq -r ' | |
if .output then | |
.output[].content[].text? | |
elif .choices then | |
.choices[].message.content? | |
else empty end' <<<"$response" | | |
sed -e 's/^```[[:alpha:]]*//' -e 's/```$//' \ | |
-e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') | |
[[ -z $cmd ]] && { echo "No command received." >&2; return 2; } | |
# ------------------ verbose block ---------------------- | |
if [[ $verbose == true ]]; then | |
echo "CURL REQUEST:" | |
echo "curl -s https://api.openai.com/v1/responses \\" | |
for h in "${headers[@]}"; do | |
echo " $h \\" | |
done | |
echo " -d '$payload'" | |
echo | |
echo "RAW RESPONSE:" | |
echo "$response" | |
echo | |
echo "PARSED COMMAND:" | |
echo "$cmd" | |
echo | |
fi | |
# ------------- preload into command line --------------- | |
if [[ -o interactive && $+functions[zle] -eq 1 ]]; then | |
BUFFER="$cmd" | |
CURSOR=${#BUFFER} | |
zle -R # redraw prompt | |
else | |
# fall back: queue it into history & next prompt | |
print -z -- "$cmd" | |
fi | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment