#!/usr/bin/env bash : <<'DOC' ask β€” AI terminal assistant (macOS) Setup API key (Keychain): security add-generic-password -a "$USER" -s TK_AI_API_TOKEN -w "YOUR_API_KEY" Usage: ask "question" ask -i # interactive mode ask -v # verbose (debug raw response) DOC # ===== CONFIG ===== SERVER="https://ai.acbpro.com/v1/chat/completions" MODEL="gpt-5.3-codex" KEY_NAME="TK_AI_API_TOKEN" VERBOSE=0 INTERACTIVE=0 # ===== COLORS ===== GREEN="\033[1;32m" CYAN="\033[1;36m" YELLOW="\033[1;33m" RED="\033[1;31m" RESET="\033[0m" # ===== PARSE FLAGS ===== while [[ "$1" == -* ]]; do case "$1" in -v) VERBOSE=1 ;; -i) INTERACTIVE=1 ;; esac shift done # ===== LOAD TOKEN ===== API_TOKEN=$(security find-generic-password -a "$USER" -s "$KEY_NAME" -w 2>/dev/null | tr -d '\n') if [ -z "$API_TOKEN" ]; then echo "❌ API token not found in Keychain ($KEY_NAME)" exit 1 fi # ===== SYSTEM PROMPT ===== SYSTEM_PROMPT="You are a terminal assistant for macOS (zsh/bash). - Default: return ONLY the command, no explanation. - If user asks for explanation, then explain. - Commands must be safe and runnable. - Prefer standard macOS/Linux tools. - No markdown, no code block. " # ===== TERMINAL STATUS ===== supports_status_line() { [ -t 2 ] && [ "${TERM:-}" != "dumb" ] } render_status_line() { supports_status_line || return 0 printf '\r\033[2K%b' "$1" >&2 } clear_status_line() { supports_status_line || return 0 printf '\r\033[2K' >&2 } print_response() { if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then echo -e "${CYAN}πŸ€– $1${RESET}" else printf '%s\n' "$1" fi } # ===== FUNCTION CALL API ===== call_api() { local Q="$1" local CWD GIT_BRANCH LAST_CMD JSON local START_TIME TMP_FILE CURL_PID local SPINNER i NOW ELAPSED CHAR COLOR local RESPONSE CONTENT CURL_STATUS CWD="$(pwd)" GIT_BRANCH="$(git branch --show-current 2>/dev/null)" LAST_CMD="$(fc -ln -1 2>/dev/null)" JSON=$(jq -n \ --arg model "$MODEL" \ --arg sys "$SYSTEM_PROMPT" \ --arg q "$Q" \ --arg cwd "$CWD" \ --arg branch "$GIT_BRANCH" \ --arg last "$LAST_CMD" \ '{ model: $model, messages: [ {role: "system", content: $sys}, {role: "user", content: ("Context: " + "cwd: " + $cwd + " " + "git_branch: " + $branch + " " + "last_command: " + $last + " " + "Request: " + $q) } ] }' ) # ===== CALL (async) ===== START_TIME=$(date +%s) TMP_FILE=$(mktemp) curl -s "$SERVER" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ -d "$JSON" > "$TMP_FILE" & CURL_PID=$! trap 'clear_status_line; [ -n "${CURL_PID:-}" ] && kill "${CURL_PID}" 2>/dev/null; [ -n "${TMP_FILE:-}" ] && rm -f "${TMP_FILE}"; exit 130' INT TERM # ===== SPINNER + TIMER ===== SPINNER='|/-\' i=0 if supports_status_line; then while kill -0 "$CURL_PID" 2>/dev/null; do NOW=$(date +%s) ELAPSED=$((NOW - START_TIME)) CHAR=${SPINNER:i%${#SPINNER}:1} if [ "$ELAPSED" -lt 10 ]; then COLOR="$GREEN"; elif [ "$ELAPSED" -lt 30 ]; then COLOR="$YELLOW"; else COLOR="$RED"; fi render_status_line "${COLOR}⏳ calling ${CHAR} ${ELAPSED}s${RESET}" i=$((i+1)) sleep 0.1 done fi wait "$CURL_PID" CURL_STATUS=$? clear_status_line trap - INT TERM RESPONSE=$(<"$TMP_FILE") rm -f "$TMP_FILE" if [ "$CURL_STATUS" -ne 0 ]; then echo "❌ request failed (curl exit $CURL_STATUS)" >&2 [ -n "$RESPONSE" ] && echo "$RESPONSE" >&2 return "$CURL_STATUS" fi if [ "$VERBOSE" -eq 1 ]; then echo -e "${YELLOW}πŸ”§ RAW RESPONSE:${RESET}" >&2 echo "$RESPONSE" >&2 fi CONTENT=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // .response // .text // .') print_response "$CONTENT" } # ===== INTERACTIVE MODE ===== if [ "$INTERACTIVE" -eq 1 ]; then echo -e "${GREEN}πŸ’¬ Interactive mode (Ctrl+C để thoΓ‘t)${RESET}" while true; do printf "${GREEN}❓ ask> ${RESET}" read Q [ -z "$Q" ] && continue call_api "$Q" done exit 0 fi # ===== NORMAL MODE ===== if [ $# -eq 0 ]; then echo "Usage: ask [-v] [-i] " exit 1 fi call_api "$*"