Created
April 8, 2026 14:40
-
-
Save StephenFluin/f48d3abb6b07f485ed765c82ba625083 to your computer and use it in GitHub Desktop.
Want to run a command but don't know the syntax? Let AI figure it out for you.
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 bash | |
| # ───────────────────────────────────────────────────────────────── | |
| # ai-cmd — natural-language → shell command via Claude or Gemini | |
| # | |
| # Usage: | |
| # !! strip the audio from obs.mp4 | |
| # !! list all jpg files larger than 5MB | |
| # !! compress this-dir into a tar.gz | |
| # | |
| # Setup: | |
| # 1. chmod +x ai-cmd.sh | |
| # 2. Set ONE of these env vars (Claude is tried first): | |
| # export ANTHROPIC_API_KEY="sk-ant-..." | |
| # export GEMINI_API_KEY="AI..." | |
| # 3. Add this alias to your .bashrc / .zshrc: | |
| # alias '!!'='source /path/to/ai-cmd.sh' | |
| # | |
| # "source" is needed so the command runs in your current shell | |
| # (inherits cd, env vars, etc.) | |
| # ───────────────────────────────────────────────────────────────── | |
| set -euo pipefail | |
| # ── Colors ─────────────────────────────────────────────────────── | |
| BOLD='\033[1m' | |
| DIM='\033[2m' | |
| CYAN='\033[36m' | |
| GREEN='\033[32m' | |
| YELLOW='\033[33m' | |
| RED='\033[31m' | |
| RESET='\033[0m' | |
| # ── Collect the prompt ─────────────────────────────────────────── | |
| PROMPT="$*" | |
| if [[ -z "$PROMPT" ]]; then | |
| echo -e "${RED}Usage:${RESET} !! <describe what you want to do>" | |
| return 0 2>/dev/null || exit 0 | |
| fi | |
| # ── Gather context ─────────────────────────────────────────────── | |
| SHELL_NAME=$(basename "$SHELL" 2>/dev/null || echo "bash") | |
| OS_INFO=$(uname -s 2>/dev/null || echo "Unknown") | |
| CWD=$(pwd) | |
| DIR_LISTING=$(ls -1 2>/dev/null | head -30) | |
| SYSTEM_PROMPT="You are a command-line assistant. The user describes a task in natural language. \ | |
| Respond with ONLY the shell command(s) to accomplish it — no explanation, no markdown fences, \ | |
| no commentary. If multiple commands are needed, separate them with && or newlines. \ | |
| Use common CLI tools (ffmpeg, tar, find, grep, sed, awk, curl, etc.). \ | |
| Context: shell=$SHELL_NAME, os=$OS_INFO, cwd=$CWD | |
| Files in cwd: | |
| $DIR_LISTING" | |
| # ── API call: Claude ───────────────────────────────────────────── | |
| call_claude() { | |
| local response | |
| response=$(curl -sS https://api.anthropic.com/v1/messages \ | |
| -H "x-api-key: $ANTHROPIC_API_KEY" \ | |
| -H "anthropic-version: 2023-06-01" \ | |
| -H "content-type: application/json" \ | |
| -d "$(jq -n \ | |
| --arg model "claude-sonnet-4-20250514" \ | |
| --arg system "$SYSTEM_PROMPT" \ | |
| --arg prompt "$PROMPT" \ | |
| '{ | |
| model: $model, | |
| max_tokens: 1024, | |
| system: $system, | |
| messages: [{role: "user", content: $prompt}] | |
| }')") | |
| # Check for errors | |
| local err | |
| err=$(echo "$response" | jq -r '.error.message // empty' 2>/dev/null) | |
| if [[ -n "$err" ]]; then | |
| echo "CLAUDE_ERROR: $err" >&2 | |
| return 1 | |
| fi | |
| echo "$response" | jq -r '.content[0].text // empty' 2>/dev/null | |
| } | |
| # ── API call: Gemini ───────────────────────────────────────────── | |
| call_gemini() { | |
| local response | |
| response=$(curl -sS \ | |
| "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ | |
| -H "content-type: application/json" \ | |
| -d "$(jq -n \ | |
| --arg system "$SYSTEM_PROMPT" \ | |
| --arg prompt "$PROMPT" \ | |
| '{ | |
| system_instruction: {parts: [{text: $system}]}, | |
| contents: [{parts: [{text: $prompt}]}] | |
| }')") | |
| # Check for errors | |
| local err | |
| err=$(echo "$response" | jq -r '.error.message // empty' 2>/dev/null) | |
| if [[ -n "$err" ]]; then | |
| echo "GEMINI_ERROR: $err" >&2 | |
| return 1 | |
| fi | |
| echo "$response" | jq -r '.candidates[0].content.parts[0].text // empty' 2>/dev/null | |
| } | |
| # ── Pick provider & call ───────────────────────────────────────── | |
| echo -e "${DIM}Thinking...${RESET}" | |
| COMMAND="" | |
| PROVIDER="" | |
| if [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then | |
| PROVIDER="Claude" | |
| COMMAND=$(call_claude 2>/dev/null) || true | |
| fi | |
| if [[ -z "$COMMAND" ]] && [[ -n "${GEMINI_API_KEY:-}" ]]; then | |
| PROVIDER="Gemini" | |
| COMMAND=$(call_gemini 2>/dev/null) || true | |
| fi | |
| if [[ -z "$COMMAND" ]]; then | |
| echo -e "${RED}Error:${RESET} No response. Check your API key." | |
| echo " Set ANTHROPIC_API_KEY or GEMINI_API_KEY in your environment." | |
| return 1 2>/dev/null || exit 1 | |
| fi | |
| # Strip any markdown fences the model might have snuck in | |
| COMMAND=$(echo "$COMMAND" | sed '/^```/d' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') | |
| # ── Present the command ────────────────────────────────────────── | |
| echo "" | |
| echo -e "${DIM}── ${PROVIDER} suggests ──────────────────────────${RESET}" | |
| echo -e "${BOLD}${CYAN}${COMMAND}${RESET}" | |
| echo -e "${DIM}────────────────────────────────────────────────${RESET}" | |
| echo "" | |
| # ── Interactive loop ───────────────────────────────────────────── | |
| while true; do | |
| echo -en " ${GREEN}[r]un${RESET} ${YELLOW}[e]dit${RESET} ${RED}[c]ancel${RESET} → " | |
| read -r -n1 CHOICE | |
| echo "" | |
| case "$CHOICE" in | |
| r|R|"") | |
| echo "" | |
| echo -e "${DIM}▶ Running...${RESET}" | |
| echo "" | |
| eval "$COMMAND" | |
| RESULT=$? | |
| echo "" | |
| if [[ $RESULT -eq 0 ]]; then | |
| echo -e "${GREEN}✔ Done (exit $RESULT)${RESET}" | |
| else | |
| echo -e "${RED}✘ Exited with code $RESULT${RESET}" | |
| fi | |
| break | |
| ;; | |
| e|E) | |
| # Pre-fill the command into readline for editing | |
| echo "" | |
| read -r -e -i "$COMMAND" -p "$(echo -e "${CYAN}Edit: ${RESET}")" COMMAND | |
| echo "" | |
| echo -e "${DIM}Updated command:${RESET}" | |
| echo -e "${BOLD}${CYAN}${COMMAND}${RESET}" | |
| echo "" | |
| # Loop back to ask run/edit/cancel again | |
| ;; | |
| c|C) | |
| echo -e "${DIM}Cancelled.${RESET}" | |
| break | |
| ;; | |
| *) | |
| echo -e "${DIM} Press r, e, or c${RESET}" | |
| ;; | |
| esac | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment