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 task "monitor cpu usage" # describe what you want to do
|
||||||
super-man category files # browse by category (files/text/network/system)
|
super-man category files # browse by category (files/text/network/system)
|
||||||
super-man explain "tar -czf" # explain what a command does
|
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
|
`Esc` in the flag browser returns to the command list; `Esc` on the home screen
|
||||||
exits.
|
exits.
|
||||||
|
|
||||||
|
|||||||
+125
-36
@@ -41,7 +41,7 @@ show_help() {
|
|||||||
echo -e " ${CYAN}Natural Language Queries:${NC}"
|
echo -e " ${CYAN}Natural Language Queries:${NC}"
|
||||||
echo -e " ${GREEN}ask${NC} \"query\" Ask in natural language (database)"
|
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}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 ""
|
||||||
echo -e " ${CYAN}Browse & Explore:${NC}"
|
echo -e " ${CYAN}Browse & Explore:${NC}"
|
||||||
echo -e " ${GREEN}category${NC} NAME Browse by category (files/text/network/system)"
|
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 -e " ${GREEN}✓${NC} fzf (for interactive mode only)"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BOLD}${YELLOW}AI MODE${NC} 🤖"
|
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 \"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 ""
|
||||||
echo " Other models available: llama3.2, qwen2.5"
|
echo -e " Default model: ${CYAN}${SUPERMAN_AI_MODEL}${NC}"
|
||||||
echo " To switch models, edit mode_ai() function"
|
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 ""
|
||||||
echo -e "${YELLOW}Database:${NC} $DB_FILE"
|
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 ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,15 +311,85 @@ mode_category() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# AI mode - use NL2SH model for complex queries
|
# 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() {
|
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
|
if [ -z "$user_query" ]; then
|
||||||
echo -e "${RED}Error: No query provided${NC}"
|
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 ""
|
||||||
echo "Example:"
|
echo "Examples:"
|
||||||
echo " super-man ai \"find all python files modified in last week\""
|
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
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -331,18 +404,50 @@ mode_ai() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if NL2SH model is available
|
# ── Compare mode: run every configured model side by side ──────────────────
|
||||||
if ! ollama list | grep -q "westenfelder/NL2SH"; then
|
if $compare; then
|
||||||
echo -e "${YELLOW}NL2SH model not found. Available models:${NC}"
|
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
|
ollama list
|
||||||
echo ""
|
echo ""
|
||||||
echo "Pull NL2SH model with:"
|
echo "Pull it with: ollama pull $model"
|
||||||
echo " ollama pull westenfelder/NL2SH"
|
echo "Or pick another: super-man ai --model NAME \"$user_query\""
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}🤖 AI Assistant - Using NL2SH Model${NC}"
|
echo -e "${CYAN}🤖 AI Assistant — $model${NC}"
|
||||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
|
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
|
||||||
echo -e "${YELLOW}Query:${NC} $user_query"
|
echo -e "${YELLOW}Query:${NC} $user_query"
|
||||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
|
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
|
||||||
@@ -350,8 +455,7 @@ mode_ai() {
|
|||||||
echo -e "${DIM}Generating bash command...${NC}"
|
echo -e "${DIM}Generating bash command...${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Query the NL2SH model
|
local response=$(ai_generate "$model" "$user_query")
|
||||||
local response=$(ollama run westenfelder/NL2SH "$user_query" 2>/dev/null)
|
|
||||||
|
|
||||||
if [ -z "$response" ]; then
|
if [ -z "$response" ]; then
|
||||||
echo -e "${RED}Error: No response from AI model${NC}"
|
echo -e "${RED}Error: No response from AI model${NC}"
|
||||||
@@ -365,19 +469,7 @@ mode_ai() {
|
|||||||
echo -e " ${MAGENTA}$response${NC}"
|
echo -e " ${MAGENTA}$response${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Extract the base command for explanation
|
ai_explain_base "$response"
|
||||||
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
|
|
||||||
|
|
||||||
echo -e "${DIM}Note: Always review AI-generated commands before running them!${NC}"
|
echo -e "${DIM}Note: Always review AI-generated commands before running them!${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -1250,8 +1342,9 @@ else
|
|||||||
QUERY="$2"
|
QUERY="$2"
|
||||||
;;
|
;;
|
||||||
ai)
|
ai)
|
||||||
MODE="ai"
|
shift
|
||||||
QUERY="$2"
|
mode_ai "$@"
|
||||||
|
exit $?
|
||||||
;;
|
;;
|
||||||
-*)
|
-*)
|
||||||
echo "Unknown option: $1"
|
echo "Unknown option: $1"
|
||||||
@@ -1280,10 +1373,6 @@ if [ -n "$MODE" ]; then
|
|||||||
mode_category "$QUERY"
|
mode_category "$QUERY"
|
||||||
exit $?
|
exit $?
|
||||||
;;
|
;;
|
||||||
ai)
|
|
||||||
mode_ai "$QUERY"
|
|
||||||
exit $?
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user