Initial commit

This commit is contained in:
2026-04-19 21:43:13 +07:00
commit b66b6589c8
6 changed files with 1206 additions and 0 deletions
Executable
+195
View File
@@ -0,0 +1,195 @@
#!/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] <question>"
exit 1
fi
call_api "$*"