diff --git a/README.md b/README.md index 76be733..a0efe50 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# ask - AI CLI tool +# ask - Claude Code CLI wrapper -A lightweight bash script for querying AI models via the OpenRouter API, optimized for direct, executable output. +A lightweight bash script for querying Claude AI via the Claude Code CLI, optimized for direct, executable output with file editing capabilities. ## Quick start @@ -12,16 +12,15 @@ cd ask chmod +x ask sudo cp ask /usr/local/bin/ - -# Make sure you have you OpenRouter API key -export OPENROUTER_API_KEY="your-api-key-here" +# Install Claude Code CLI first +# Visit: https://claude.com/claude-code # Test it -> ask remove lines in file1 that appear in file2 +ask "Write a hello world in Python" -grep -vFf file2 file1 > file3 && mv file3 file1 +print("Hello, World!") -[inception/mercury-coder via Inception - 0.66s - 20.9 tok/s] +[sonnet - 1.23s - 0 tool calls - 12 tokens - $0.0001] ``` We also provide a handy install script. @@ -31,36 +30,24 @@ We also provide a handy install script. ### Basic usage ```bash -ask ffmpeg command to convert mp4 to gif +ask "ffmpeg command to convert mp4 to gif" ``` ### Model selection ```bash -# Default model (Mercury Coder - optimized for code) -ask find files larger than 20mb +# Default model (Claude Sonnet) +ask "find files larger than 20mb" # Shorthand flags for quick model switching -ask -c "prompt" # Mercury Coder (default, best for code) -ask -g "prompt" # Gemini 2.5 Flash (fast, general purpose) -ask -s "prompt" # Claude Sonnet 4 (complex reasoning) -ask -k "prompt" # Kimi K2 (long context) -ask -q "prompt" # Qwen 235B (large model) - -# Custom model by full name -ask -m "openai/gpt-4o" "Explain this concept" -``` +ask -s "prompt" # Claude Sonnet (default, balanced) +ask -o "prompt" # Claude Opus (most capable, complex reasoning) +ask -h "prompt" # Claude Haiku (fastest, simple tasks) -### Provider routing - -Specify provider order for fallback support: - -```bash -ask --provider "cerebras,together" "Generate code" +# Custom model by name +ask -m haiku "What is 2+2?" ``` -This will try Cerebras first, then fall back to Together if needed. - ### System prompts ```bash @@ -71,36 +58,60 @@ ask --system "You are a pirate" "Tell me about sailing" ask -r "What is 2+2?" ``` -### Streaming mode +### File editing -Get responses as they're generated: +File editing is **enabled by default** in the current working directory: ```bash -ask --stream "Tell me a long story" +# Claude can read and edit files automatically +ask "Read README.md and update it with current features" +ask "Fix the bug in server.js" + +# Disable file editing if you only want analysis +ask --no-edits "Just analyze server.js, don't modify it" +``` + +### Running commands + +Claude can run bash commands to gather context: + +```bash +# Claude will run git commands for you +ask "Look at git diff and write a commit message" +ask "Run git status and summarize changes" + +# Show statistics to see tool usage +ask --stats "Run git status and summarize changes" ``` ### Pipe input ```bash +# Pipe as prompt echo "Fix this code: print('hello world)" | ask -cat script.py | ask "Review this code" + +# Pipe as context with --context flag +git diff | ask --context "Write a commit message for this diff" +cat error.log | ask --context "Explain what caused this error" ``` ## Options | Option | Description | |--------|-------------| -| `-c` | Use Mercury Coder (default) | -| `-g` | Use Google Gemini 2.5 Flash | -| `-s` | Use Claude Sonnet 4 | -| `-k` | Use Moonshotai Kimi K2 | -| `-q` | Use Qwen3 235B | -| `-m MODEL` | Use custom model | -| `-r` | Disable system prompt | -| `--stream` | Enable streaming output | -| `--system` | Set custom system prompt | -| `--provider` | Set provider order (comma-separated) | -| `-h, --help` | Show help message | +| `-s` | Use Claude Sonnet (default) | +| `-o` | Use Claude Opus | +| `-h` | Use Claude Haiku | +| `-m MODEL` | Use custom Claude model | +| `-r` | Disable system prompt (raw model behavior) | +| `--system TEXT` | Set custom system prompt | +| `--context` | Pipe stdin as context, combine with prompt argument | +| `--no-edits` | Disable file editing (enabled by default) | +| `--permission-mode MODE` | Set permission mode (e.g., acceptEdits) | +| `--allowed-tools TOOLS` | Comma-separated list of allowed tools | +| `--stats` | Show statistics (time, tool calls, tokens, cost) | +| `--debug` | Show system context, prompts, and command (stderr) | +| `--help` | Show help message | ## Common use cases @@ -123,6 +134,29 @@ ask "Python function to calculate factorial" cat script.py | ask "Find potential bugs in this code" ``` +### File editing and code modification +```bash +# Claude can read and modify files directly +ask "Read server.js and add error handling to the API routes" +ask "Fix the typo in README.md line 42" +ask "Refactor utils.py to use type hints" + +# Disable editing for read-only analysis +ask --no-edits "Analyze the performance of this algorithm in sort.js" +``` + +### Git workflow +```bash +# Claude can run git commands and analyze changes +ask "Look at git diff and write a commit message" +ask "Run git status and tell me what needs attention" +ask "Review the last 3 commits and summarize changes" + +# See what Claude is doing with --stats +ask --stats "Run git log --oneline -5 and summarize the recent changes" +# Output: [sonnet - 2.15s - 1 tool calls - 89 tokens - $0.0023] +``` + ### Quick answers ```bash # Calculations @@ -142,19 +176,34 @@ ask "List all Python files" | ask "Generate a script to check syntax of these fi # Use with other tools docker ps -a | ask "Which containers are using the most memory?" +# Debug mode to see what's happening +ask --debug "Why is my build failing?" ``` ## Requirements ### Dependencies - `bash` - Shell interpreter -- `curl` - HTTP requests to OpenRouter API - `jq` - JSON parsing for API responses - `bc` - Performance metrics calculation - -### API access -- OpenRouter API key (get one at [openrouter.ai](https://openrouter.ai)) -- Set as environment variable: `OPENROUTER_API_KEY` +- Claude Code CLI - The underlying AI engine + +### Installation +1. Install Claude Code CLI from [claude.com/claude-code](https://claude.com/claude-code) +2. Clone this repository +3. Make the script executable: `chmod +x ask` +4. Copy to your PATH: `sudo cp ask /usr/local/bin/` + +## Features + +- **Direct executable output** - Optimized for command generation and piping +- **File editing** - Claude can read and modify files in your working directory +- **Command execution** - Claude can run bash commands to gather context +- **Smart system context** - Automatically detects OS, tools, git repos, and project type +- **Model selection** - Choose between Sonnet, Opus, or Haiku +- **Statistics** - Track tool usage, tokens, and costs with `--stats` +- **Debug mode** - See exactly what prompts are being sent +- **Caching** - System context is cached for 24 hours for performance ## License diff --git a/ask b/ask index 6ee2b3f..b96c278 100755 --- a/ask +++ b/ask @@ -1,36 +1,42 @@ -#!/bin/bash +#!/usr/bin/env bash -# ask - Simple OpenRouter API CLI tool +# ask - Simple Claude Code CLI wrapper # Usage: ask [OPTIONS] [PROMPT] set -euo pipefail -# Check for API key -if [ -z "${OPENROUTER_API_KEY:-}" ]; then - echo "Error: OPENROUTER_API_KEY environment variable is not set" >&2 +# Check for Claude CLI +if ! command -v claude &> /dev/null; then + echo "Error: claude command not found. Install Claude Code CLI first." >&2 + echo "Visit: https://claude.com/claude-code" >&2 exit 1 fi # Model shortcuts function get_model() { case "$1" in - c) echo "inception/mercury-coder:nitro" ;; - g) echo "google/gemini-2.5-flash-preview-09-2025:nitro" ;; - s) echo "anthropic/claude-sonnet-4.5:nitro" ;; - x) echo "x-ai/grok-code-fast-1:nitro" ;; - k) echo "moonshotai/kimi-k2:nitro" ;; - q) echo "qwen/qwen3-235b-a22b-2507:nitro" ;; - o) echo "openai/gpt-5:nitro" ;; + s) echo "sonnet" ;; + o) echo "opus" ;; + h) echo "haiku" ;; esac } +# System information caching +CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/ask" +CACHE_FILE="$CACHE_DIR/sysinfo" +CACHE_MAX_AGE=86400 # 24 hours in seconds + # Default values -MODEL="qwen/qwen3-235b-a22b-2507:nitro" +MODEL="sonnet" SYSTEM_PROMPT="" PROMPT="" -STREAMING=false NO_SYSTEM=false -PROVIDER_ORDER="" +DEBUG=false +WITH_CONTEXT=false +SHOW_STATS=false +ALLOW_EDITS=true # Enable file edits by default +PERMISSION_MODE="" +ALLOWED_TOOLS="" # Default system prompt (direct answers) DEFAULT_PROMPT="You are a direct answer engine. Output ONLY the requested information. @@ -50,41 +56,218 @@ Rules: # Function to show help show_help() { cat << EOF -ask - Query AI models via OpenRouter API +ask - Query Claude AI via Claude Code CLI Usage: ask [OPTIONS] [PROMPT] Options: - -c Use inception/mercury-coder - -g Use google/gemini-2.5-flash-preview-09-2025 - -s Use anthropic/claude-sonnet-4.5 - -x Use x-ai/grok-code-fast-1 - -k Use moonshotai/kimi-k2 - -q Use qwen/qwen3-235b-a22b-2507 (default) - -o Use openai/gpt-5 - -m MODEL Use custom model - -r Disable system prompt (raw model behavior) - --stream Enable streaming output - --system Set system prompt for the conversation - --provider Comma-separated list of providers for routing - -h, --help Show this help message + -s Use Claude Sonnet (default) + -o Use Claude Opus + -h Use Claude Haiku + -m MODEL Use custom Claude model + -r Disable system prompt (raw model behavior) + --system TEXT Set custom system prompt for the conversation + --context Pipe stdin as context, combine with prompt argument + --no-edits Disable file editing (enabled by default) + --permission-mode MODE Set permission mode (e.g., acceptEdits) + --allowed-tools TOOLS Comma-separated list of allowed tools + --stats Show statistics (time, tool calls, tokens, cost) + --debug Show system context, prompts, and command (stderr) + --help Show this help message + +Note: File editing is enabled by default in the current working directory. Examples: + # Basic usage ask "Write a hello world in Python" - ask -g "Explain quantum computing" - ask -m openai/gpt-4o "What is 2+2?" + ask -o "Explain quantum computing" + ask -h "What is 2+2?" + + # Pipe input as prompt echo "Fix this code" | ask + + # Claude can run commands (recommended for git diffs) + ask "Look at git diff and write a commit message" + ask "Run git status and summarize changes" + + # File editing (enabled by default) + ask "Read README.md and update it with current features" + ask "Fix the bug in server.js" + ask --no-edits "Just analyze server.js, don't modify it" + + # Pipe context with --context flag (alternative) + git diff | ask --context "Write a commit message for this diff" + cat error.log | ask --context "Explain what caused this error" + + # Show stats to see tool usage and cost + ask --stats "Run git status and summarize changes" + # Output: [sonnet - 8.23s - 1 tool calls - 142 tokens - \$0.0145] + + # Other options ask --system "You are a pirate" "Tell me about sailing" + ask -r "Tell me a joke" + ask --debug "Show me debug output" EOF exit 0 } +# Gather system information +gather_sysinfo() { + # OS and Distribution + local os_info=$(grep -E '^(NAME|VERSION_ID)=' /etc/os-release 2>/dev/null | \ + cut -d= -f2 | tr -d '"' | paste -sd ' ' 2>/dev/null || \ + echo "$(uname -s) $(uname -r)") + + # Architecture + local arch=$(uname -m) + + # Package Manager + local pkg_mgr="unknown" + for pm in apt dnf yum pacman nix brew; do + if command -v "$pm" >/dev/null 2>&1; then + pkg_mgr="$pm" + break + fi + done + + # Shell + local shell_name=$(basename "$SHELL" 2>/dev/null || echo "bash") + + # Development Tools + local tools="" + for tool in python3 node npm docker git make gcc nix pip cargo go java rustc; do + command -v "$tool" >/dev/null 2>&1 && tools="${tools}${tool}," + done + tools=${tools%,} # Remove trailing comma + + # Working Directory + local wd=$(pwd) + + # Git Repository + local in_git="no" + git rev-parse --is-inside-work-tree >/dev/null 2>&1 && in_git="yes" + + # Project Type + local proj_type="none" + if [ -f package.json ]; then proj_type="node" + elif [ -f requirements.txt ] || [ -f pyproject.toml ]; then proj_type="python" + elif [ -f Cargo.toml ]; then proj_type="rust" + elif [ -f go.mod ]; then proj_type="go" + elif [ -f pom.xml ] || [ -f build.gradle ]; then proj_type="java" + elif [ -f Makefile ]; then proj_type="make" + fi + + # Format output (one line per item for easy parsing) + cat << EOF +OS=$os_info +ARCH=$arch +PKG=$pkg_mgr +SHELL=$shell_name +TOOLS=$tools +WD=$wd +GIT=$in_git +PROJECT=$proj_type +EOF +} + +# Check if cache should be refreshed +should_refresh_cache() { + [ ! -f "$CACHE_FILE" ] && return 0 + + # Try to get file modification time + local cache_mtime + if cache_mtime=$(stat -c %Y "$CACHE_FILE" 2>/dev/null); then + : # GNU stat worked + elif cache_mtime=$(stat -f %m "$CACHE_FILE" 2>/dev/null); then + : # BSD stat worked + else + return 0 # Can't determine age, refresh + fi + + local now=$(date +%s) + local age=$((now - cache_mtime)) + + [ "$age" -gt "$CACHE_MAX_AGE" ] && return 0 + return 1 +} + +# Get system context (cached) +get_system_context() { + # Create cache directory if needed + if [ ! -d "$CACHE_DIR" ]; then + mkdir -p "$CACHE_DIR" 2>/dev/null || { + # Fallback to temp + CACHE_DIR="/tmp/ask-${USER:-default}" + CACHE_FILE="$CACHE_DIR/sysinfo" + mkdir -p "$CACHE_DIR" 2>/dev/null || return 1 + } + fi + + # Refresh cache if needed + if should_refresh_cache; then + gather_sysinfo > "$CACHE_FILE" 2>/dev/null || return 1 + fi + + # Read and format cache + if [ -f "$CACHE_FILE" ]; then + # Parse cache file + local os_info arch pkg_mgr shell_name tools wd in_git proj_type + + while IFS='=' read -r key value; do + case "$key" in + OS) os_info="$value" ;; + ARCH) arch="$value" ;; + PKG) pkg_mgr="$value" ;; + SHELL) shell_name="$value" ;; + TOOLS) tools="$value" ;; + WD) wd="$value" ;; + GIT) in_git="$value" ;; + PROJECT) proj_type="$value" ;; + esac + done < "$CACHE_FILE" + + # Format concise context + local context="System: $os_info $arch | Shell: $shell_name | Pkg: $pkg_mgr" + [ -n "$tools" ] && context="$context +Tools: $tools" + + local ctx_detail="Context: $wd" + [ "$in_git" = "yes" ] && ctx_detail="$ctx_detail | Git repo" + [ "$proj_type" != "none" ] && ctx_detail="$ctx_detail | ${proj_type} project" + context="$context +$ctx_detail" + + echo "$context" + fi +} + # Parse command line arguments while [ $# -gt 0 ]; do case "$1" in - -h|--help) show_help ;; - -[cgskqxo]) + --help) show_help ;; + --debug) + DEBUG=true + shift ;; + --stats) + SHOW_STATS=true + shift ;; + --context) + WITH_CONTEXT=true + shift ;; + --allow-edits) + ALLOW_EDITS=true + shift ;; + --no-edits) + ALLOW_EDITS=false + shift ;; + --permission-mode) + PERMISSION_MODE="${2:?Error: --permission-mode requires a value}" + shift 2 ;; + --allowed-tools) + ALLOWED_TOOLS="${2:?Error: --allowed-tools requires a value}" + shift 2 ;; + -[soh]) MODEL="$(get_model "${1:1}")" shift ;; -m) @@ -93,29 +276,55 @@ while [ $# -gt 0 ]; do -r) NO_SYSTEM=true shift ;; - --stream) - STREAMING=true - shift ;; --system) SYSTEM_PROMPT="${2:?Error: --system requires a prompt}" shift 2 ;; - --provider) - PROVIDER_ORDER="${2:?Error: --provider requires providers}" - shift 2 ;; *) PROMPT="$*" break ;; esac done -# If no prompt provided as argument, read from stdin -if [ -z "$PROMPT" ]; then +# Handle stdin and prompt +if [ "$WITH_CONTEXT" = true ]; then + # --context mode: read stdin as context, combine with prompt + if [ ! -t 0 ]; then + STDIN_CONTENT=$(cat) + if [ -z "$PROMPT" ]; then + echo "Error: --context requires both stdin and a prompt argument" >&2 + exit 1 + fi + # Combine stdin content with prompt + PROMPT="${STDIN_CONTENT} + +--- + +${PROMPT}" + else + echo "Error: --context requires stdin input. Use: command | ask --context \"prompt\"" >&2 + exit 1 + fi +elif [ -z "$PROMPT" ]; then + # No prompt argument: read from stdin if [ -t 0 ]; then - echo "Error: No prompt provided. Use 'ask -h' for help." >&2 + echo "Error: No prompt provided. Use 'ask --help' for help." >&2 exit 1 fi # Read all stdin, preserving multi-line format PROMPT=$(cat) +else + # Prompt provided: warn if stdin is being ignored + if [ ! -t 0 ]; then + echo "Warning: stdin detected but will be ignored. Use --context to include it." >&2 + # Consume stdin to prevent it from being passed to claude + cat > /dev/null + fi +fi + +# Gather system context (unless in raw mode) +SYSTEM_CONTEXT="" +if [ "$NO_SYSTEM" = false ]; then + SYSTEM_CONTEXT=$(get_system_context) fi # Apply default system prompt unless disabled or custom prompt provided @@ -123,83 +332,147 @@ if [ "$NO_SYSTEM" = false ] && [ -z "$SYSTEM_PROMPT" ]; then SYSTEM_PROMPT="$DEFAULT_PROMPT" fi -# Build messages array with proper JSON escaping -if [ -n "$SYSTEM_PROMPT" ]; then - MESSAGES='[{"role":"system","content":'"$(printf '%s' "$SYSTEM_PROMPT" | jq -Rs .)"'},{"role":"user","content":'"$(printf '%s' "$PROMPT" | jq -Rs .)"'}]' -else - MESSAGES='[{"role":"user","content":'"$(printf '%s' "$PROMPT" | jq -Rs .)"'}]' +# Prepend system context to system prompt +if [ -n "$SYSTEM_CONTEXT" ] && [ -n "$SYSTEM_PROMPT" ]; then + SYSTEM_PROMPT="${SYSTEM_CONTEXT} + +${SYSTEM_PROMPT}" fi # Record start time START_TIME=$(date +%s.%N) -# Build JSON payload once -PROVIDER_JSON="" -if [ -n "$PROVIDER_ORDER" ]; then - PROVIDER_JSON=',"provider":{"order":['$(echo "$PROVIDER_ORDER" | awk -F, '{for(i=1;i<=NF;i++) printf "\"%s\"%s", $i, (i&2 + if [ -n "$SYSTEM_CONTEXT" ]; then + echo "$SYSTEM_CONTEXT" >&2 + else + echo "(none)" >&2 + fi + echo >&2 + + echo "=== SYSTEM PROMPT ===" >&2 + if [ -n "$SYSTEM_PROMPT" ]; then + echo "$SYSTEM_PROMPT" >&2 + else + echo "(none)" >&2 + fi + echo >&2 -API_URL="https://openrouter.ai/api/v1/chat/completions" + echo "=== USER PROMPT ===" >&2 + echo "$PROMPT" >&2 + echo >&2 + + echo "=== CLAUDE COMMAND ===" >&2 + echo "claude $CLAUDE_ARGS" | sed 's/"[^"]*"/"<...>"/g' >&2 + echo >&2 +fi # Add newline before answer echo -# Make API request -if [ "$STREAMING" = true ]; then - # Streaming mode - curl -sS "$API_URL" \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENROUTER_API_KEY" \ - -d "$JSON_PAYLOAD" 2>&1 | while IFS= read -r line; do - # Check for errors - if echo "$line" | grep -q '"error"'; then - echo "Error: $(echo "$line" | jq -r '.error.message // .error // "Unknown error"')" >&2 - exit 1 - fi +# Execute Claude CLI +response=$(eval claude $CLAUDE_ARGS 2>&1) +exit_code=$? - # Process SSE data lines - if [[ "$line" == data:* ]]; then - json="${line#data: }" - [ "$json" = "" ] || [ "$json" = "[DONE]" ] && continue +# Debug output for response +if [ "$DEBUG" = true ]; then + echo "=== RAW RESPONSE ===" >&2 + echo "$response" >&2 + echo >&2 +fi - content=$(echo "$json" | jq -r '.choices[0].delta.content // ""' 2>/dev/null) - [ -n "$content" ] && printf '%s' "$content" - fi - done - echo +# Check for errors +if [ $exit_code -ne 0 ]; then + echo "Error: Claude CLI failed" >&2 + echo "$response" >&2 + exit 1 +fi - # Show metadata - ELAPSED=$(printf "%.2f" $(echo "$(date +%s.%N) - $START_TIME" | bc)) - echo - echo "[$MODEL - ${ELAPSED}s]" >&2 -else - # Non-streaming mode - response=$(curl -sS "$API_URL" \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENROUTER_API_KEY" \ - -d "$JSON_PAYLOAD" 2>&1) - - # Check for errors - if echo "$response" | grep -q '"error"'; then - echo "Error: $(echo "$response" | jq -r '.error.message // .error // "Unknown error"')" >&2 +# Extract and print content from JSON response +content=$(echo "$response" | jq -r '.result // empty' 2>/dev/null) + +# Check if result is empty or if there were permission denials +if [ -z "$content" ]; then + # Check for permission denials + denials=$(echo "$response" | jq -r '.permission_denials // [] | length' 2>/dev/null) + if [ "$denials" != "0" ] && [ "$denials" != "null" ]; then + echo "Error: Claude tried to perform an action but was denied permission." >&2 + echo "" >&2 + echo "Denied actions:" >&2 + echo "$response" | jq -r '.permission_denials[] | " - \(.tool_name)"' 2>/dev/null >&2 + echo "" >&2 + echo "This may happen if:" >&2 + echo " 1. File is outside current working directory" >&2 + echo " 2. Insufficient file system permissions" >&2 + echo " 3. Run: claude (interactive mode) for more control" >&2 + exit 1 + else + # No content and no denials - unexpected + echo "Error: No response content received" >&2 + [ "$DEBUG" = true ] && echo "$response" >&2 exit 1 fi +fi - # Extract and print content - echo "$response" | jq -r '.choices[0].message.content // "No response received"' +echo "$content" + +# Show metadata (only if --stats enabled) +if [ "$SHOW_STATS" = true ]; then + # ANSI color codes + GRAY='\033[90m' + BLUE='\033[94m' + GREEN='\033[92m' + YELLOW='\033[93m' + RESET='\033[0m' - # Show metadata ELAPSED=$(printf "%.2f" $(echo "$(date +%s.%N) - $START_TIME" | bc)) - TOKENS=$(echo "$response" | jq -r '.usage.completion_tokens // 0') - PROVIDER=$(echo "$response" | jq -r '.provider // "Unknown"') - TPS=$(echo "scale=1; $TOKENS / $ELAPSED" | bc 2>/dev/null || echo "0.0") + INPUT_TOKENS=$(echo "$response" | jq -r '.usage.input_tokens // .input_tokens // 0' 2>/dev/null) + OUTPUT_TOKENS=$(echo "$response" | jq -r '.usage.output_tokens // .output_tokens // 0' 2>/dev/null) + NUM_TURNS=$(echo "$response" | jq -r '.num_turns // 0' 2>/dev/null) + COST=$(echo "$response" | jq -r '.total_cost_usd // 0' 2>/dev/null) + + echo >&2 + # Build metadata string with colors + metadata="${GRAY}[${BLUE}${MODEL}${GRAY} - ${GREEN}${ELAPSED}s${RESET}" + + # Calculate tool calls (turns - 1) + if [ "$NUM_TURNS" != "0" ] && [ "$NUM_TURNS" != "null" ]; then + TOOL_CALLS=$((NUM_TURNS - 1)) + metadata="$metadata${GRAY} - ${YELLOW}${TOOL_CALLS} tool calls${RESET}" + fi + + # Add tokens and cost/tps if available + if [ "$OUTPUT_TOKENS" != "0" ] && [ "$OUTPUT_TOKENS" != "null" ]; then + metadata="$metadata${GRAY} - ${RESET}${OUTPUT_TOKENS} tokens" + + # Show cost if available (rounded to 4 decimal places), otherwise show tok/s + if [ "$COST" != "0" ] && [ "$COST" != "null" ]; then + ROUNDED_COST=$(printf "%.4f" "$COST") + metadata="$metadata${GRAY} - ${GREEN}\$${ROUNDED_COST}${RESET}" + else + TPS=$(echo "scale=1; $OUTPUT_TOKENS / $ELAPSED" | bc 2>/dev/null || echo "0.0") + metadata="$metadata${GRAY} - ${GREEN}${TPS} tok/s${RESET}" + fi + fi - echo - echo "[$MODEL via $PROVIDER - ${ELAPSED}s - ${TPS} tok/s]" >&2 + echo -e "$metadata${GRAY}]${RESET}" >&2 fi diff --git a/install.sh b/install.sh deleted file mode 100755 index db9ec35..0000000 --- a/install.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -# install.sh - Install ask CLI tool system-wide - -set -euo pipefail - -# Colors -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -echo "Installing 'ask' CLI tool..." - -# Check if script exists -if [ ! -f "ask" ]; then - echo "Error: 'ask' script not found in current directory" >&2 - exit 1 -fi - -# Make executable -chmod +x ask - -# Install to /usr/local/bin -echo "Installing to /usr/local/bin/ask (requires sudo)..." -sudo cp ask /usr/local/bin/ - -echo -e "${GREEN}✓ Installation complete!${NC}" -echo - -# Get OpenRouter IPs -echo "Resolving OpenRouter DNS..." -IPS=$(dig +short openrouter.ai 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' || nslookup openrouter.ai 2>/dev/null | grep -A1 "Name:" | grep "Address:" | awk '{print $2}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$') - -if [ -n "$IPS" ]; then - echo -e "${YELLOW}OpenRouter IP addresses:${NC}" - echo "$IPS" - echo - echo "To improve performance, you can add these to /etc/hosts:" - echo "----------------------------------------" - for IP in $IPS; do - echo "$IP openrouter.ai" - done | head -1 # Only show first IP as hosts file needs single entry - echo "----------------------------------------" - echo - echo "Add with: sudo nano /etc/hosts" - echo "(Only add ONE IP address to avoid conflicts)" -else - echo "Could not resolve OpenRouter IPs. Network may be unavailable." -fi - -echo -echo -e "${GREEN}Usage:${NC}" -echo " ask 'What is 2+2?'" -echo " ask -g 'Explain quantum computing'" -echo " ask --help" -echo -echo "Don't forget to set your API key:" -echo " export OPENROUTER_API_KEY='your-key-here'" \ No newline at end of file