feat(ai): make ai mode model-configurable + add --compare for A/B testing
Default the ai mode to qwen2.5-coder:1.5b instead of the hardcoded (and uninstalled) westenfelder/NL2SH, which left ai mode broken. The model is now configurable via SUPERMAN_AI_MODEL / --model, with markdown-fence stripping and a coder-specific prompt wrapper so general coder models emit a single bare one-liner. NL2SH still receives the raw query. Adds --compare to run multiple models side by side (SUPERMAN_AI_COMPARE_MODELS) with timing for benchmarking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,9 +28,29 @@ super-man ask "find large files" # ask a question
|
||||
super-man task "monitor cpu usage" # describe what you want to do
|
||||
super-man category files # browse by category (files/text/network/system)
|
||||
super-man explain "tar -czf" # explain what a command does
|
||||
super-man ai "count lines in js files" # AI-powered NL2SH model (advanced)
|
||||
super-man ai "count lines in js files" # AI bash one-liner via local LLM (advanced)
|
||||
```
|
||||
|
||||
### AI mode (local LLM via ollama)
|
||||
|
||||
`ai` generates a bash one-liner from plain English using a local [ollama](https://ollama.com)
|
||||
model. The model is configurable, so you can A/B different local LLMs:
|
||||
|
||||
```bash
|
||||
super-man ai "find python files modified last week" # default model
|
||||
super-man ai --model qwen2.5:3b "count lines in all js files" # override model
|
||||
super-man ai --compare "list the 5 largest files" # run several models side by side
|
||||
```
|
||||
|
||||
| Env var | Purpose | Default |
|
||||
|---------|---------|---------|
|
||||
| `SUPERMAN_AI_MODEL` | Default model for `ai` | `qwen2.5-coder:1.5b` |
|
||||
| `SUPERMAN_AI_COMPARE_MODELS` | Space-separated list for `--compare` | `qwen2.5-coder:1.5b westenfelder/NL2SH` |
|
||||
|
||||
General coder models (e.g. `qwen2.5-coder`) are prompt-wrapped to emit a single bare
|
||||
command; the purpose-built `westenfelder/NL2SH` model receives the raw query. Pull
|
||||
either with `ollama pull <model>`.
|
||||
|
||||
`Esc` in the flag browser returns to the command list; `Esc` on the home screen
|
||||
exits.
|
||||
|
||||
|
||||
+125
-36
@@ -41,7 +41,7 @@ show_help() {
|
||||
echo -e " ${CYAN}Natural Language Queries:${NC}"
|
||||
echo -e " ${GREEN}ask${NC} \"query\" Ask in natural language (database)"
|
||||
echo -e " ${GREEN}task${NC} \"description\" Describe what you want to do (database)"
|
||||
echo -e " ${GREEN}ai${NC} \"complex query\" AI-powered NL2SH model (advanced) 🤖"
|
||||
echo -e " ${GREEN}ai${NC} \"complex query\" AI bash one-liner via local LLM (advanced) 🤖"
|
||||
echo ""
|
||||
echo -e " ${CYAN}Browse & Explore:${NC}"
|
||||
echo -e " ${GREEN}category${NC} NAME Browse by category (files/text/network/system)"
|
||||
@@ -77,15 +77,18 @@ show_help() {
|
||||
echo -e " ${GREEN}✓${NC} fzf (for interactive mode only)"
|
||||
echo ""
|
||||
echo -e "${BOLD}${YELLOW}AI MODE${NC} 🤖"
|
||||
echo -e " ${GREEN}✓ NL2SH model detected!${NC} Use AI for complex queries:"
|
||||
echo -e " Generate bash one-liners from plain English via a local ollama model:"
|
||||
echo -e " ${GREEN}super-man ai \"find python files modified last week\"${NC}"
|
||||
echo -e " ${GREEN}super-man ai \"count lines in all js files\"${NC}"
|
||||
echo -e " ${GREEN}super-man ai --model qwen2.5:3b \"count lines in all js files\"${NC}"
|
||||
echo -e " ${GREEN}super-man ai --compare \"list the 5 largest files\"${NC} # A/B models"
|
||||
echo ""
|
||||
echo " Other models available: llama3.2, qwen2.5"
|
||||
echo " To switch models, edit mode_ai() function"
|
||||
echo -e " Default model: ${CYAN}${SUPERMAN_AI_MODEL}${NC}"
|
||||
echo " Configure via env vars:"
|
||||
echo " SUPERMAN_AI_MODEL default model (e.g. westenfelder/NL2SH)"
|
||||
echo " SUPERMAN_AI_COMPARE_MODELS space-separated list for --compare"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Database:${NC} $DB_FILE"
|
||||
echo -e "${YELLOW}Version:${NC} 2.1.0 (250+ commands, AI-powered)"
|
||||
echo -e "${YELLOW}Version:${NC} 2.2.0 (250+ commands, configurable local AI)"
|
||||
echo ""
|
||||
}
|
||||
|
||||
@@ -308,15 +311,85 @@ mode_category() {
|
||||
}
|
||||
|
||||
# AI mode - use NL2SH model for complex queries
|
||||
# Generate bash one-liners from natural language via a local ollama model.
|
||||
# The model is configurable so we can A/B different local LLMs:
|
||||
# SUPERMAN_AI_MODEL default model (default: qwen2.5-coder:1.5b)
|
||||
# SUPERMAN_AI_COMPARE_MODELS space-separated list used by `ai --compare`
|
||||
# Override per call with `--model NAME`; run every comparison model side by
|
||||
# side with `--compare`. NL2SH is purpose-built for NL->shell so it receives
|
||||
# the raw query; general coder models get an instruction wrapper plus markdown
|
||||
# fence stripping so they too emit a single bare command.
|
||||
SUPERMAN_AI_MODEL="${SUPERMAN_AI_MODEL:-qwen2.5-coder:1.5b}"
|
||||
SUPERMAN_AI_COMPARE_MODELS="${SUPERMAN_AI_COMPARE_MODELS:-qwen2.5-coder:1.5b westenfelder/NL2SH}"
|
||||
|
||||
# Build a model-appropriate prompt for a query.
|
||||
ai_build_prompt() {
|
||||
local model="$1" query="$2"
|
||||
case "$model" in
|
||||
*NL2SH*|*nl2sh*)
|
||||
printf '%s' "$query"
|
||||
;;
|
||||
*)
|
||||
printf 'You are a bash expert. Output ONLY a single bash one-liner that accomplishes the task. No explanation, no comments, no markdown code fences. Task: %s' "$query"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Strip markdown fences and surrounding blank lines from a raw model response.
|
||||
ai_clean_response() {
|
||||
awk '/^[[:space:]]*```/ { next } { print }' \
|
||||
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' \
|
||||
| sed '/^$/d'
|
||||
}
|
||||
|
||||
# True if an ollama model is pulled locally.
|
||||
ai_model_installed() {
|
||||
ollama list 2>/dev/null | grep -q "$1"
|
||||
}
|
||||
|
||||
# Generate a cleaned command from one model for a query.
|
||||
ai_generate() {
|
||||
local model="$1" query="$2"
|
||||
ollama run "$model" "$(ai_build_prompt "$model" "$query")" 2>/dev/null | ai_clean_response
|
||||
}
|
||||
|
||||
# Print the man/help NAME line for the base command of a generated one-liner.
|
||||
ai_explain_base() {
|
||||
local base_cmd
|
||||
base_cmd=$(echo "$1" | awk '{print $1}')
|
||||
if type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then
|
||||
echo -e "${YELLOW}Command Info:${NC}"
|
||||
help "$base_cmd" 2>/dev/null | head -3
|
||||
echo ""
|
||||
elif command -v "$base_cmd" &> /dev/null; then
|
||||
echo -e "${YELLOW}Command Info:${NC}"
|
||||
man "$base_cmd" 2>/dev/null | col -b | grep -A 2 "^NAME" | tail -2
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
mode_ai() {
|
||||
local user_query="$1"
|
||||
local compare=false
|
||||
local model="$SUPERMAN_AI_MODEL"
|
||||
local args=()
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--compare|-c) compare=true ;;
|
||||
--model|-m) shift; model="$1" ;;
|
||||
*) args+=("$1") ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
local user_query="${args[*]}"
|
||||
|
||||
if [ -z "$user_query" ]; then
|
||||
echo -e "${RED}Error: No query provided${NC}"
|
||||
echo "Usage: super-man ai \"your complex query\""
|
||||
echo "Usage: super-man ai [--model NAME|--compare] \"your query\""
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo "Examples:"
|
||||
echo " super-man ai \"find all python files modified in last week\""
|
||||
echo " super-man ai --model qwen2.5:3b \"count lines in all js files\""
|
||||
echo " super-man ai --compare \"list the 5 largest files\""
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -331,18 +404,50 @@ mode_ai() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if NL2SH model is available
|
||||
if ! ollama list | grep -q "westenfelder/NL2SH"; then
|
||||
echo -e "${YELLOW}NL2SH model not found. Available models:${NC}"
|
||||
# ── Compare mode: run every configured model side by side ──────────────────
|
||||
if $compare; then
|
||||
echo ""
|
||||
echo -e "${CYAN}🤖 AI Compare${NC}"
|
||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${YELLOW}Query:${NC} $user_query"
|
||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
|
||||
local m
|
||||
for m in $SUPERMAN_AI_COMPARE_MODELS; do
|
||||
echo ""
|
||||
echo -e "${BOLD}${MAGENTA}▸ $m${NC}"
|
||||
if ! ai_model_installed "$m"; then
|
||||
echo -e " ${DIM}not installed — pull with: ollama pull $m${NC}"
|
||||
continue
|
||||
fi
|
||||
local start end elapsed out
|
||||
start=$(date +%s.%N)
|
||||
out=$(ai_generate "$m" "$user_query")
|
||||
end=$(date +%s.%N)
|
||||
elapsed=$(awk "BEGIN{printf \"%.1f\", $end-$start}")
|
||||
if [ -z "$out" ]; then
|
||||
echo -e " ${RED}(no response)${NC} ${DIM}(${elapsed}s)${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}$out${NC} ${DIM}(${elapsed}s)${NC}"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
echo -e "${DIM}Note: Always review AI-generated commands before running them!${NC}"
|
||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ── Single-model mode ──────────────────────────────────────────────────────
|
||||
if ! ai_model_installed "$model"; then
|
||||
echo -e "${YELLOW}Model '$model' not found. Installed models:${NC}"
|
||||
ollama list
|
||||
echo ""
|
||||
echo "Pull NL2SH model with:"
|
||||
echo " ollama pull westenfelder/NL2SH"
|
||||
echo "Pull it with: ollama pull $model"
|
||||
echo "Or pick another: super-man ai --model NAME \"$user_query\""
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}🤖 AI Assistant - Using NL2SH Model${NC}"
|
||||
echo -e "${CYAN}🤖 AI Assistant — $model${NC}"
|
||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${YELLOW}Query:${NC} $user_query"
|
||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
|
||||
@@ -350,8 +455,7 @@ mode_ai() {
|
||||
echo -e "${DIM}Generating bash command...${NC}"
|
||||
echo ""
|
||||
|
||||
# Query the NL2SH model
|
||||
local response=$(ollama run westenfelder/NL2SH "$user_query" 2>/dev/null)
|
||||
local response=$(ai_generate "$model" "$user_query")
|
||||
|
||||
if [ -z "$response" ]; then
|
||||
echo -e "${RED}Error: No response from AI model${NC}"
|
||||
@@ -365,19 +469,7 @@ mode_ai() {
|
||||
echo -e " ${MAGENTA}$response${NC}"
|
||||
echo ""
|
||||
|
||||
# Extract the base command for explanation
|
||||
local base_cmd=$(echo "$response" | awk '{print $1}')
|
||||
|
||||
# Try to provide explanation
|
||||
if command -v "$base_cmd" &> /dev/null || type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then
|
||||
echo -e "${YELLOW}Command Info:${NC}"
|
||||
if type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then
|
||||
help "$base_cmd" 2>/dev/null | head -3
|
||||
else
|
||||
man "$base_cmd" 2>/dev/null | col -b | grep -A 2 "^NAME" | tail -2
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
ai_explain_base "$response"
|
||||
|
||||
echo -e "${DIM}Note: Always review AI-generated commands before running them!${NC}"
|
||||
echo ""
|
||||
@@ -1250,8 +1342,9 @@ else
|
||||
QUERY="$2"
|
||||
;;
|
||||
ai)
|
||||
MODE="ai"
|
||||
QUERY="$2"
|
||||
shift
|
||||
mode_ai "$@"
|
||||
exit $?
|
||||
;;
|
||||
-*)
|
||||
echo "Unknown option: $1"
|
||||
@@ -1280,10 +1373,6 @@ if [ -n "$MODE" ]; then
|
||||
mode_category "$QUERY"
|
||||
exit $?
|
||||
;;
|
||||
ai)
|
||||
mode_ai "$QUERY"
|
||||
exit $?
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user