diff --git a/askChatGPT b/askChatGPT index 96180da..fb2069c 100755 --- a/askChatGPT +++ b/askChatGPT @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# askChatGPT - A simple Bash client for OpenAI Chat API +# askChatGPT - Bash client for OpenAI Chat API with token-usage to stderr # Requires: curl, jq -# Env var: OPENAI_API_KEY must be set +# Env: OPENAI_API_KEY must be set set -euo pipefail @@ -9,7 +9,8 @@ MODEL="gpt-4o-mini" PREPEND_TEXT="" CONTENT_FILE="" LIST_MODELS=0 -declare -a PROMPT_WORDS=() # <-- important: initialize the array +STREAM=0 +declare -a PROMPT_WORDS=() usage() { cat < Text to prepend before the main prompt -content-from-file Read prompt content from file - -h, --help Show this help message + --stream Stream output (still reports usage to stderr) + -h, --help Show this help EOF } # Parse args while [[ $# -gt 0 ]]; do - case "$1" in - -model) - MODEL="$2"; shift 2 ;; - --list-models) - LIST_MODELS=1; shift ;; - -prepend) - PREPEND_TEXT="$2"; shift 2 ;; - -content-from-file) - CONTENT_FILE="$2"; shift 2 ;; - -h|--help) - usage; exit 0 ;; - *) - PROMPT_WORDS+=("$1"); shift ;; - esac + case "$1" in + -model) MODEL="$2"; shift 2 ;; + --list-models) LIST_MODELS=1; shift ;; + -prepend) PREPEND_TEXT="$2"; shift 2 ;; + -content-from-file) CONTENT_FILE="$2"; shift 2 ;; + --stream) STREAM=1; shift ;; + -h|--help) usage; exit 0 ;; + *) PROMPT_WORDS+=("$1"); shift ;; + esac done -# Check deps +# Deps for dep in curl jq; do - command -v "$dep" >/dev/null 2>&1 || { echo "Error: '$dep' is required."; exit 1; } + command -v "$dep" >/dev/null 2>&1 || { echo "Error: '$dep' is required." >&2; exit 1; } done -# Check API key +# API key if [[ -z "${OPENAI_API_KEY:-}" ]]; then - echo "Error: OPENAI_API_KEY is not set." - exit 1 + echo "Error: OPENAI_API_KEY is not set." >&2 + exit 1 fi # List models if [[ "$LIST_MODELS" -eq 1 ]]; then - curl -s https://api.openai.com/v1/models \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - | jq -r '.data[].id' | sort - exit 0 + curl -sS https://api.openai.com/v1/models \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + | jq -r '.data[].id' | sort + exit 0 fi # Build prompt PROMPT_MAIN="" if [[ -n "$CONTENT_FILE" ]]; then - [[ -f "$CONTENT_FILE" ]] || { echo "Error: file '$CONTENT_FILE' not found."; exit 1; } - PROMPT_MAIN="$(<"$CONTENT_FILE")" + [[ -f "$CONTENT_FILE" ]] || { echo "Error: file '$CONTENT_FILE' not found." >&2; exit 1; } + PROMPT_MAIN="$(<"$CONTENT_FILE")" elif (( ${#PROMPT_WORDS[@]} > 0 )); then - PROMPT_MAIN="${PROMPT_WORDS[*]}" + PROMPT_MAIN="${PROMPT_WORDS[*]}" else - if ! [ -t 0 ]; then - PROMPT_MAIN="$(cat)" - else - echo "Error: No prompt provided." - usage - exit 1 - fi + if ! [ -t 0 ]; then + PROMPT_MAIN="$(cat)" + else + echo "Error: No prompt provided." >&2 + usage + exit 1 + fi fi PROMPT_FULL="" if [[ -n "$PREPEND_TEXT" ]]; then - PROMPT_FULL+="$PREPEND_TEXT"$'\n\n' + PROMPT_FULL+="$PREPEND_TEXT"$'\n\n' fi PROMPT_FULL+="$PROMPT_MAIN" -# JSON-escape content safely JSON_CONTENT=$(printf '%s' "$PROMPT_FULL" | jq -Rs .) -# Send request -RESPONSE=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d "$(cat <&2 + fi + +else + # -------- Streaming ---------- + # We request usage in the final streamed chunk + # Note: final chunk will have .usage populated and .choices == [] + CURL_RESP=$( + curl -sS -N "$API_URL" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d "$(cat </dev/null || true) + if [[ -n "$chunk_content" ]]; then + printf "%s" "$chunk_content" + fi + + # If this chunk carries usage, print to stderr + if [[ "$(printf '%s' "$payload" | jq 'has("usage")' 2>/dev/null || echo false)" == "true" ]]; then + PT=$(printf '%s' "$payload" | jq -r '.usage.prompt_tokens // 0') + CT=$(printf '%s' "$payload" | jq -r '.usage.completion_tokens // 0') + TT=$(printf '%s' "$payload" | jq -r '.usage.total_tokens // 0') + echo "" # ensure the streamed answer ends with newline on stdout + echo "tokens: prompt=$PT completion=$CT total=$TT" >&2 + USAGE_PRINTED=1 + fi + done <<< "$CURL_RESP" + + # Safety: if for some reason usage didn’t arrive, still end with newline + if [[ $USAGE_PRINTED -eq 0 ]]; then + echo "" + fi +fi