Files
ollama-cli/ollama

1263 lines
34 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
SCRIPT_VERSION="1.1.1"
# Default values (should be configured in ~/.ollama/config.json)
DEFAULT_OLLAMA_API_URL="https://ollama.local"
DEFAULT_OLLAMA_USER="ollama"
DEFAULT_OLLAMA_PASS_PATH=""
DEFAULT_OLLAMA_PASSWORD=""
DEFAULT_MODEL_DEFAULT="llama2"
# Configuration file
CONFIG_FILE="${HOME}/.ollama/config.json"
# Load configuration from config file
load_config() {
if [[ -f "$CONFIG_FILE" ]]; then
# Validate JSON first
if ! jq empty "$CONFIG_FILE" 2>/dev/null; then
echo "Error: Invalid JSON in config file: $CONFIG_FILE" >&2
return 1
fi
# Load values from config file, with fallback to empty if keys don't exist
OLLAMA_API_URL=$(jq -r '.api_url // empty' "$CONFIG_FILE") || return 1
OLLAMA_USER=$(jq -r '.user // empty' "$CONFIG_FILE") || return 1
OLLAMA_PASS_PATH=$(jq -r '.password_path // empty' "$CONFIG_FILE") || return 1
OLLAMA_PASSWORD=$(jq -r '.password // empty' "$CONFIG_FILE") || return 1
DEFAULT_MODEL=$(jq -r '.default_model // empty' "$CONFIG_FILE") || return 1
fi
return 0
}
# Apply configuration with environment variable overrides
load_config || exit 1
OLLAMA_API_URL="${OLLAMA_API_URL_ENV:-${OLLAMA_API_URL:-$DEFAULT_OLLAMA_API_URL}}"
OLLAMA_USER="${OLLAMA_USER_ENV:-${OLLAMA_USER:-$DEFAULT_OLLAMA_USER}}"
OLLAMA_PASS_PATH="${OLLAMA_PASS_PATH_ENV:-${OLLAMA_PASS_PATH:-$DEFAULT_OLLAMA_PASS_PATH}}"
OLLAMA_PASSWORD="${OLLAMA_PASSWORD_ENV:-${OLLAMA_PASSWORD:-$DEFAULT_OLLAMA_PASSWORD}}"
DEFAULT_MODEL="${DEFAULT_MODEL_ENV:-${DEFAULT_MODEL:-$DEFAULT_MODEL_DEFAULT}}"
# Allow direct env var overrides (OLLAMA_API_URL, OLLAMA_USER, OLLAMA_PASS_PATH, OLLAMA_PASSWORD, DEFAULT_MODEL)
OLLAMA_API_URL="${OLLAMA_API_URL:-$DEFAULT_OLLAMA_API_URL}"
OLLAMA_USER="${OLLAMA_USER:-$DEFAULT_OLLAMA_USER}"
OLLAMA_PASS_PATH="${OLLAMA_PASS_PATH:-$DEFAULT_OLLAMA_PASS_PATH}"
OLLAMA_PASSWORD="${OLLAMA_PASSWORD:-$DEFAULT_OLLAMA_PASSWORD}"
DEFAULT_MODEL="${DEFAULT_MODEL:-$DEFAULT_MODEL_DEFAULT}"
CONTEXT_FILE="${OLLAMA_CONTEXT_FILE:-$HOME/.ollama/context.json}"
PROFILE_DIR="${OLLAMA_PROFILE_DIR:-$HOME/.ollama/profiles}"
OUTPUT_FORMAT="text" # Can be "text" or "json"
# Check for required dependencies
check_dependencies() {
local missing=()
# Always required
for cmd in curl jq column less; do
if ! command -v "$cmd" &> /dev/null; then
missing+=("$cmd")
fi
done
# Only required if using pass for password
if [[ -n "$OLLAMA_PASS_PATH" ]]; then
if ! command -v pass &> /dev/null; then
missing+=("pass")
fi
fi
if [[ ${#missing[@]} -gt 0 ]]; then
echo "Error: missing required command(s): ${missing[*]}" >&2
return 1
fi
}
check_dependencies
# Ensure ~/.ollama directory exists
ensure_ollama_dir() {
local dir="$(dirname "$CONTEXT_FILE")"
if [[ ! -d "$dir" ]]; then
mkdir -p "$dir"
fi
}
# Initialize empty context file if it doesn't exist
init_context_file() {
ensure_ollama_dir
if [[ ! -f "$CONTEXT_FILE" ]]; then
echo '[]' > "$CONTEXT_FILE"
fi
}
# Load system prompt from profile
load_profile_system() {
local profile_name="$1"
local profile_file="$PROFILE_DIR/$profile_name.json"
if [[ -f "$profile_file" ]]; then
jq -r '.system // ""' "$profile_file"
fi
}
# Load pre-script from profile
load_profile_pre_script() {
local profile_name="$1"
local profile_file="$PROFILE_DIR/$profile_name.json"
if [[ -f "$profile_file" ]]; then
jq -r '.pre_script // ""' "$profile_file"
fi
}
# Load post-script from profile
load_profile_post_script() {
local profile_name="$1"
local profile_file="$PROFILE_DIR/$profile_name.json"
if [[ -f "$profile_file" ]]; then
jq -r '.post_script // ""' "$profile_file"
fi
}
# Load model from profile
load_profile_model() {
local profile_name="$1"
local profile_file="$PROFILE_DIR/$profile_name.json"
if [[ -f "$profile_file" ]]; then
jq -r '.model // ""' "$profile_file"
fi
}
# Execute a script with input
execute_script() {
local script="$1"
local input="$2"
if [[ -n "$script" ]]; then
local output
output=$(echo "$input" | bash -c "$script" 2>&1)
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
echo "Error: script execution failed with exit code $exit_code" >&2
echo "Script output: $output" >&2
return $exit_code
fi
echo "$output"
else
echo "$input"
fi
}
# Ensure default profile exists
ensure_default_profile() {
local default_profile="$PROFILE_DIR/default.json"
if [[ ! -f "$default_profile" ]]; then
ensure_ollama_dir
if [[ ! -d "$PROFILE_DIR" ]]; then
mkdir -p "$PROFILE_DIR"
fi
echo "You are a helpful assistant" | jq -n -R '{system: input}' > "$default_profile"
fi
}
# Get password from direct config or from pass
# Returns empty string if no password configured (for servers without auth)
get_password() {
if [[ -n "$OLLAMA_PASSWORD" ]]; then
echo "$OLLAMA_PASSWORD"
elif [[ -n "$OLLAMA_PASS_PATH" ]]; then
pass "$OLLAMA_PASS_PATH"
else
echo ""
fi
}
# Generate a completion
generate() {
local prompt=""
local model="$DEFAULT_MODEL"
local profile="default"
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-m|--model)
model="$2"
shift 2
;;
-p|--profile)
profile="$2"
shift 2
;;
*)
prompt="$1"
shift
;;
esac
done
# Read from stdin if available and combine with argument prompt
local stdin_content=""
if [[ ! -t 0 ]]; then
stdin_content=$(cat)
fi
if [[ -n "$stdin_content" ]] && [[ -n "$prompt" ]]; then
prompt="$stdin_content"$'\n'"$prompt"
elif [[ -n "$stdin_content" ]]; then
prompt="$stdin_content"
elif [[ -z "$prompt" ]]; then
echo "Error: prompt required (provide as argument or via stdin)"
return 1
fi
local password
password=$(get_password)
# Ensure default profile exists
ensure_default_profile
# Load system prompt and scripts from profile
local system_prompt
system_prompt=$(load_profile_system "$profile")
local pre_script
pre_script=$(load_profile_pre_script "$profile")
local post_script
post_script=$(load_profile_post_script "$profile")
# Apply pre-script if defined
prompt=$(execute_script "$pre_script" "$prompt")
# If system prompt provided, use chat API; otherwise use generate API
local response
local api_response
if [[ -n "$system_prompt" ]]; then
# Build messages array with system prompt
local messages
messages=$(echo '[]' | jq --arg content "$system_prompt" '. += [{"role": "system", "content": $content}]')
messages=$(echo "$messages" | jq --arg content "$prompt" '. += [{"role": "user", "content": $content}]')
api_response=$(curl -s -u "$OLLAMA_USER:$password" \
-X POST "$OLLAMA_API_URL/chat" \
-H 'Content-Type: application/json' \
-d "{
\"model\": \"$model\",
\"messages\": $messages,
\"stream\": false
}")
# Check for API errors
if ! echo "$api_response" | jq . &>/dev/null; then
echo "Error: Invalid response from API" >&2
echo "Response: $api_response" >&2
return 1
fi
if echo "$api_response" | jq -e '.error' &>/dev/null; then
echo "Error: $(echo "$api_response" | jq -r '.error')" >&2
return 1
fi
response=$(echo "$api_response" | jq -r '.message.content // empty')
if [[ -z "$response" ]]; then
echo "Error: No response content from API" >&2
return 1
fi
else
# Use generate API for simple completion
api_response=$(curl -s -u "$OLLAMA_USER:$password" \
-X POST "$OLLAMA_API_URL/generate" \
-H 'Content-Type: application/json' \
-d "{
\"model\": \"$model\",
\"prompt\": $(printf '%s\n' "$prompt" | jq -R .),
\"stream\": false
}")
# Check for API errors
if ! echo "$api_response" | jq . &>/dev/null; then
echo "Error: Invalid response from API" >&2
echo "Response: $api_response" >&2
return 1
fi
if echo "$api_response" | jq -e '.error' &>/dev/null; then
echo "Error: $(echo "$api_response" | jq -r '.error')" >&2
return 1
fi
response=$(echo "$api_response" | jq -r '.response // empty')
if [[ -z "$response" ]]; then
echo "Error: No response content from API" >&2
return 1
fi
fi
# Apply post-script if defined
if ! response=$(execute_script "$post_script" "$response"); then
return 1
fi
echo "$response"
}
# Chat with context
chat() {
local prompt=""
local model="$DEFAULT_MODEL"
local reset=false
local profile="default"
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-m|--model)
model="$2"
shift 2
;;
-c|--context)
CONTEXT_FILE="$2"
shift 2
;;
-p|--profile)
profile="$2"
shift 2
;;
-r|--reset)
reset=true
shift
;;
*)
prompt="$1"
shift
;;
esac
done
# Read from stdin if available and combine with argument prompt
local stdin_content=""
if [[ ! -t 0 ]]; then
stdin_content=$(cat)
fi
if [[ -n "$stdin_content" ]] && [[ -n "$prompt" ]]; then
prompt="$stdin_content"$'\n'"$prompt"
elif [[ -n "$stdin_content" ]]; then
prompt="$stdin_content"
elif [[ -z "$prompt" ]]; then
echo "Error: prompt required (provide as argument or via stdin)"
return 1
fi
init_context_file
# Reset context if requested
if [[ "$reset" == true ]]; then
echo '[]' > "$CONTEXT_FILE"
fi
# Ensure default profile exists
ensure_default_profile
# Load system prompt and scripts from profile
local system_prompt
system_prompt=$(load_profile_system "$profile")
local pre_script
pre_script=$(load_profile_pre_script "$profile")
local post_script
post_script=$(load_profile_post_script "$profile")
# Apply pre-script if defined
prompt=$(execute_script "$pre_script" "$prompt")
# Load existing messages
local messages
messages=$(cat "$CONTEXT_FILE")
# Build messages array with system prompt at the beginning
if [[ -n "$system_prompt" ]]; then
messages=$(echo '[]' | jq --arg content "$system_prompt" '. += [{"role": "system", "content": $content}]')
messages=$(echo "$messages" | jq --argjson context "$(cat "$CONTEXT_FILE")" '. += $context')
fi
# Add user message to context
messages=$(echo "$messages" | jq --arg content "$prompt" '. += [{"role": "user", "content": $content}]')
local password
password=$(get_password)
# Call API and get response
local api_response
api_response=$(curl -s -u "$OLLAMA_USER:$password" \
-X POST "$OLLAMA_API_URL/chat" \
-H 'Content-Type: application/json' \
-d "{
\"model\": \"$model\",
\"messages\": $messages,
\"stream\": false
}")
# Check for API errors
if ! echo "$api_response" | jq . &>/dev/null; then
echo "Error: Invalid response from API" >&2
echo "Response: $api_response" >&2
return 1
fi
if echo "$api_response" | jq -e '.error' &>/dev/null; then
echo "Error: $(echo "$api_response" | jq -r '.error')" >&2
return 1
fi
# Extract the response
local reply
reply=$(echo "$api_response" | jq -r '.message.content // empty')
if [[ -z "$reply" ]]; then
echo "Error: No response content from API" >&2
return 1
fi
# Apply post-script if defined
if ! reply=$(execute_script "$post_script" "$reply"); then
return 1
fi
echo "$reply"
# Add assistant response to context and save
local context_messages
context_messages=$(cat "$CONTEXT_FILE")
context_messages=$(echo "$context_messages" | jq --arg content "$prompt" '. += [{"role": "user", "content": $content}]')
context_messages=$(echo "$context_messages" | jq --arg content "$reply" '. += [{"role": "assistant", "content": $content}]')
echo "$context_messages" > "$CONTEXT_FILE"
}
# Generate embeddings
embed() {
local input=""
local model="$DEFAULT_MODEL"
local profile="default"
local output_format="$OUTPUT_FORMAT"
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-m|--model)
model="$2"
shift 2
;;
-p|--profile)
profile="$2"
shift 2
;;
-o|--output)
output_format="$2"
shift 2
;;
*)
input="$1"
shift
;;
esac
done
# Read from stdin if available and combine with argument input
local stdin_content=""
if [[ ! -t 0 ]]; then
stdin_content=$(cat)
fi
if [[ -n "$stdin_content" ]] && [[ -n "$input" ]]; then
input="$stdin_content"$'\n'"$input"
elif [[ -n "$stdin_content" ]]; then
input="$stdin_content"
elif [[ -z "$input" ]]; then
echo "Error: input required (provide as argument or via stdin)"
return 1
fi
local password
password=$(get_password)
# Ensure default profile exists
ensure_default_profile
# Load model from profile if not explicitly set
local profile_model
profile_model=$(load_profile_model "$profile")
if [[ -n "$profile_model" ]] && [[ "$model" == "$DEFAULT_MODEL" ]]; then
model="$profile_model"
fi
# Call embedding API
local api_response
api_response=$(curl -s -u "$OLLAMA_USER:$password" \
-X POST "$OLLAMA_API_URL/embed" \
-H 'Content-Type: application/json' \
-d "{
\"model\": \"$model\",
\"input\": $(printf '%s\n' "$input" | jq -R .),
\"stream\": false
}")
# Check for API errors
if ! echo "$api_response" | jq . &>/dev/null; then
echo "Error: Invalid response from API" >&2
echo "Response: $api_response" >&2
return 1
fi
if echo "$api_response" | jq -e '.error' &>/dev/null; then
echo "Error: $(echo "$api_response" | jq -r '.error')" >&2
return 1
fi
if [[ "$output_format" == "json" ]]; then
echo "$api_response" | jq '.'
else
# Output as formatted text with summary
local embedding
embedding=$(echo "$api_response" | jq -r '.embeddings[0] | @json' 2>/dev/null)
if [[ -z "$embedding" ]]; then
echo "Error: No embedding in response" >&2
return 1
fi
local dimension
dimension=$(echo "$api_response" | jq '.embeddings[0] | length')
echo "Embedding Summary"
echo ""
local table_data="Dimension|$dimension"
table_data="$table_data"$'\n'"Model|$model"
echo -e "$table_data" | column -t -s '|'
echo ""
echo "First 10 values:"
echo "$api_response" | jq '.embeddings[0][0:10]'
fi
}
# Manage models
model() {
if [[ $# -eq 0 ]]; then
echo "Error: model subcommand required (list, get, ps)" >&2
return 1
fi
local subcommand="$1"
shift
local output_format="$OUTPUT_FORMAT"
local password
password=$(get_password)
# Parse output format flag
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
output_format="$2"
shift 2
;;
*)
break
;;
esac
done
case "$subcommand" in
list)
# List all available models
local api_response
api_response=$(curl -s -u "$OLLAMA_USER:$password" \
-X GET "$OLLAMA_API_URL/tags")
if ! echo "$api_response" | jq . &>/dev/null; then
echo "Error: Invalid response from API" >&2
return 1
fi
if echo "$api_response" | jq -e '.error' &>/dev/null; then
echo "Error: $(echo "$api_response" | jq -r '.error')" >&2
return 1
fi
if [[ "$output_format" == "json" ]]; then
echo "$api_response" | jq '.'
else
# Build pipe-separated table data for column formatting
local table_data="NAME|SIZE|QUANTIZATION"
table_data="$table_data"$'\n'"$(echo "$api_response" | jq -r '.models[] | "\(.name)|\(.details.parameter_size)|\(.details.quantization_level)"')"
echo -e "$table_data" | column -t -s '|'
fi
;;
get)
if [[ $# -eq 0 ]]; then
echo "Error: model name required" >&2
return 1
fi
local model_name="$1"
shift
# Check for output format flag
local get_output_format="$output_format"
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
get_output_format="$2"
shift 2
;;
*)
shift
;;
esac
done
# Get detailed model info
local api_response
api_response=$(curl -s -u "$OLLAMA_USER:$password" \
-X POST "$OLLAMA_API_URL/show" \
-H 'Content-Type: application/json' \
-d "{\"name\": \"$model_name\"}")
if ! echo "$api_response" | jq . &>/dev/null; then
echo "Error: Invalid response from API" >&2
return 1
fi
if echo "$api_response" | jq -e '.error' &>/dev/null; then
echo "Error: $(echo "$api_response" | jq -r '.error')" >&2
return 1
fi
if [[ "$get_output_format" == "json" ]]; then
echo "$api_response" | jq '.'
else
# Output as formatted table with column
local table_data="Model|$model_name"
local param_size
param_size=$(echo "$api_response" | jq -r '.details.parameter_size // "-"')
table_data="$table_data"$'\n'"Parameter Size|$param_size"
local quantization
quantization=$(echo "$api_response" | jq -r '.details.quantization_level // "-"')
table_data="$table_data"$'\n'"Quantization|$quantization"
local format
format=$(echo "$api_response" | jq -r '.details.format // "-"')
table_data="$table_data"$'\n'"Format|$format"
local family
family=$(echo "$api_response" | jq -r '.details.family // "-"')
table_data="$table_data"$'\n'"Family|$family"
echo -e "$table_data" | column -t -s '|'
fi
;;
ps)
# List running models
local api_response
api_response=$(curl -s -u "$OLLAMA_USER:$password" \
-X GET "$OLLAMA_API_URL/ps")
if ! echo "$api_response" | jq . &>/dev/null; then
echo "Error: Invalid response from API" >&2
return 1
fi
if echo "$api_response" | jq -e '.error' &>/dev/null; then
echo "Error: $(echo "$api_response" | jq -r '.error')" >&2
return 1
fi
# Format running models as table
local table_data="NAME|SIZE|VRAM"
if echo "$api_response" | jq -e '.models[] | select(.name)' &>/dev/null; then
table_data="$table_data"$'\n'"$(echo "$api_response" | jq -r '.models[] | "\(.name)|\(.size)|\(.size_vram)"')"
fi
if [[ "$output_format" == "json" ]]; then
echo "$api_response" | jq '.'
else
echo -e "$table_data" | column -t -s '|'
fi
;;
*)
echo "Error: unknown model subcommand '$subcommand'" >&2
echo "Available subcommands: list, get, ps" >&2
return 1
;;
esac
}
# Manage profiles
profile() {
if [[ $# -eq 0 ]]; then
echo "Error: profile subcommand required (list, add, update, delete)"
return 1
fi
local subcommand="$1"
shift
local output_format="$OUTPUT_FORMAT"
# Parse output format flag
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
output_format="$2"
shift 2
;;
*)
break
;;
esac
done
case "$subcommand" in
list)
if [[ ! -d "$PROFILE_DIR" ]]; then
return 0
fi
if [[ ! "$(ls -A "$PROFILE_DIR"/*.json 2>/dev/null)" ]]; then
return 0
fi
# Build pipe-separated table data and use column for formatting
local table_data="NAME|SYSTEM PROMPT|MODEL|SCRIPTS"
for file in "$PROFILE_DIR"/*.json; do
local name
name=$(basename "$file" .json)
local system
system=$(jq -r '.system // ""' "$file" | cut -c1-40)
local model
model=$(jq -r '.model // ""' "$file")
local pre_script post_script scripts_str
pre_script=$(jq -r '.pre_script // ""' "$file")
post_script=$(jq -r '.post_script // ""' "$file")
scripts_str=""
[[ -n "$pre_script" ]] && scripts_str="pre"
[[ -n "$post_script" ]] && scripts_str="${scripts_str:+$scripts_str,}post"
table_data="$table_data"$'\n'"$name|$system|$model|$scripts_str"
done
if [[ "$output_format" == "json" ]]; then
# Output as JSON array of profiles
local json_output="[]"
for file in "$PROFILE_DIR"/*.json; do
local profile_name=$(basename "$file" .json)
local profile_data
profile_data=$(cat "$file" | jq --arg name "$profile_name" '. + {name: $name}')
json_output=$(echo "$json_output" | jq --argjson profile "$profile_data" '. += [$profile]')
done
echo "$json_output" | jq '.'
else
# Use column for clean table formatting
echo -e "$table_data" | column -t -s '|'
fi
;;
get)
if [[ $# -eq 0 ]]; then
echo "Error: profile name required" >&2
return 1
fi
local name="$1"
shift
# Check for output format flag
local get_output_format="$output_format"
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
get_output_format="$2"
shift 2
;;
*)
shift
;;
esac
done
local profile_file="$PROFILE_DIR/$name.json"
if [[ ! -f "$profile_file" ]]; then
echo "Error: profile '$name' not found" >&2
return 1
fi
if [[ "$get_output_format" == "json" ]]; then
# Output as JSON with profile name
cat "$profile_file" | jq --arg name "$name" '. + {name: $name}'
else
# Output as formatted table with column
local table_data="Name|$name"
local system_prompt
system_prompt=$(jq -r '.system // ""' "$profile_file")
if [[ -n "$system_prompt" ]]; then
# Truncate to 60 chars
table_data="$table_data"$'\n'"System Prompt|$(echo "$system_prompt" | cut -c1-60)..."
fi
local pre_script
pre_script=$(jq -r '.pre_script // ""' "$profile_file")
if [[ -n "$pre_script" ]]; then
table_data="$table_data"$'\n'"Pre-script|$(echo "$pre_script" | cut -c1-60)..."
fi
local post_script
post_script=$(jq -r '.post_script // ""' "$profile_file")
if [[ -n "$post_script" ]]; then
table_data="$table_data"$'\n'"Post-script|$(echo "$post_script" | cut -c1-60)..."
fi
local profile_model
profile_model=$(jq -r '.model // ""' "$profile_file")
if [[ -n "$profile_model" ]]; then
table_data="$table_data"$'\n'"Model|$profile_model"
fi
echo -e "$table_data" | column -t -s '|'
fi
;;
add)
if [[ $# -eq 0 ]]; then
echo "Error: profile name required"
return 1
fi
local name="$1"
shift
local system_prompt=""
local pre_script=""
local post_script=""
local profile_model=""
while [[ $# -gt 0 ]]; do
case "$1" in
--pre-script)
pre_script="$2"
shift 2
;;
--post-script)
post_script="$2"
shift 2
;;
-m|--model)
profile_model="$2"
shift 2
;;
*)
if [[ -z "$system_prompt" ]]; then
system_prompt="$1"
fi
shift
;;
esac
done
if [[ -z "$system_prompt" ]] && [[ ! -t 0 ]]; then
system_prompt=$(cat)
fi
if [[ -z "$system_prompt" ]]; then
echo "Error: system prompt required"
return 1
fi
ensure_ollama_dir
if [[ ! -d "$PROFILE_DIR" ]]; then
mkdir -p "$PROFILE_DIR"
fi
# Build JSON object with system prompt and optional scripts/model
local profile_json
profile_json=$(echo "$system_prompt" | jq -n -R '{system: input}')
if [[ -n "$pre_script" ]]; then
profile_json=$(echo "$profile_json" | jq --arg pre "$pre_script" '.pre_script = $pre')
fi
if [[ -n "$post_script" ]]; then
profile_json=$(echo "$profile_json" | jq --arg post "$post_script" '.post_script = $post')
fi
if [[ -n "$profile_model" ]]; then
profile_json=$(echo "$profile_json" | jq --arg model "$profile_model" '.model = $model')
fi
echo "$profile_json" > "$PROFILE_DIR/$name.json"
echo "Profile '$name' created"
;;
update)
if [[ $# -eq 0 ]]; then
echo "Error: profile name required"
return 1
fi
local name="$1"
shift
local profile_file="$PROFILE_DIR/$name.json"
if [[ ! -f "$profile_file" ]]; then
echo "Error: profile '$name' not found"
return 1
fi
local system_prompt=""
local pre_script=""
local post_script=""
local profile_model=""
while [[ $# -gt 0 ]]; do
case "$1" in
--pre-script)
pre_script="$2"
shift 2
;;
--post-script)
post_script="$2"
shift 2
;;
-m|--model)
profile_model="$2"
shift 2
;;
*)
if [[ -z "$system_prompt" ]]; then
system_prompt="$1"
fi
shift
;;
esac
done
if [[ -z "$system_prompt" ]] && [[ ! -t 0 ]]; then
system_prompt=$(cat)
fi
# Load existing profile if no updates provided
if [[ -z "$system_prompt" ]] && [[ -z "$pre_script" ]] && [[ -z "$post_script" ]] && [[ -z "$profile_model" ]]; then
echo "Error: system prompt or script/model options required"
return 1
fi
# Load current profile
local profile_json
profile_json=$(cat "$profile_file")
# Update fields if provided
if [[ -n "$system_prompt" ]]; then
profile_json=$(echo "$profile_json" | jq --arg system "$system_prompt" '.system = $system')
fi
if [[ -n "$pre_script" ]]; then
profile_json=$(echo "$profile_json" | jq --arg pre "$pre_script" '.pre_script = $pre')
fi
if [[ -n "$post_script" ]]; then
profile_json=$(echo "$profile_json" | jq --arg post "$post_script" '.post_script = $post')
fi
if [[ -n "$profile_model" ]]; then
profile_json=$(echo "$profile_json" | jq --arg model "$profile_model" '.model = $model')
fi
echo "$profile_json" > "$profile_file"
echo "Profile '$name' updated"
;;
delete)
if [[ $# -eq 0 ]]; then
echo "Error: profile name required"
return 1
fi
local name="$1"
local profile_file="$PROFILE_DIR/$name.json"
if [[ ! -f "$profile_file" ]]; then
echo "Error: profile '$name' not found"
return 1
fi
rm "$profile_file"
echo "Profile '$name' deleted"
;;
*)
echo "Error: unknown profile subcommand '$subcommand'" >&2
echo "Available subcommands: list, get, add, update, delete" >&2
return 1
;;
esac
}
# Show general help
show_help() {
cat << 'EOF'
Usage: ollama <command> [options]
Commands:
generate <prompt> [options] Generate a response from a prompt (supports stdin)
chat [options] Chat with context memory (supports stdin)
embed <text> [options] Generate embeddings for text (supports stdin)
model <subcommand> Manage models
profile <subcommand> Manage profiles
version Show script and Ollama server version
help [command] Show this help message or help for a specific command
For detailed help on a command, run:
ollama help <command>
Examples:
ollama help generate
ollama help chat
ollama help embed
ollama help profile
ollama help model
Environment Variables:
OLLAMA_CONTEXT_FILE Override default context file location
OLLAMA_PROFILE_DIR Override default profile directory location
EOF
}
help_generate() {
cat << 'EOF'
Usage: ollama generate <prompt> [options]
Generate a single response from a prompt. Supports reading from stdin.
Options:
-m, --model MODEL Use a specific model (default: llama3:8b)
-p, --profile NAME Use a profile's system prompt (default: default)
Examples:
ollama generate "What is 2+2?"
ollama generate "Write a poem" -m deepseek-r1:8b
echo "Tell me a joke" | ollama generate
echo "How do I read a file?" | ollama generate -p python
ollama generate -p json "List colors"
EOF
}
help_chat() {
cat << 'EOF'
Usage: ollama chat [options]
Chat with context memory. Maintains conversation history between invocations.
Options:
-m, --model MODEL Use a specific model (default: llama3:8b)
-c, --context FILE Use a specific context file (default: ~/.ollama/context.json)
-p, --profile NAME Use a profile's system prompt (default: default)
-r, --reset Clear the context and start fresh
Examples:
ollama chat
echo "Hello!" | ollama chat
ollama chat -m deepseek-r1:14b
ollama chat -r # Reset context
ollama chat -c /tmp/custom_context.json # Use custom context file
OLLAMA_CONTEXT_FILE=/tmp/session.json ollama chat # Use env variable
ollama chat -p default
echo "What is Docker?" | ollama chat -p json
echo "How do I write a loop?" | ollama chat -p python
EOF
}
help_profile() {
cat << 'EOF'
Usage: ollama profile <subcommand> [options]
Manage profiles for system prompts, pre/post scripts, and model selection.
Subcommands:
list List all profile names
get NAME Show profile details
add NAME PROMPT [OPTIONS] Create a new profile
update NAME [PROMPT] [OPTIONS] Update a profile's system prompt or scripts
delete NAME Delete a profile
Profile Options:
--pre-script SCRIPT Script to execute on input before sending to ollama
--post-script SCRIPT Script to execute on output after receiving from ollama
-m, --model MODEL Model to use for this profile
Examples:
ollama profile list # List all profiles
ollama profile get json # Show profile details
ollama profile add work "You are a professional..." # Create profile
ollama profile add uppercase "Respond in all caps" --pre-script "tr '[:lower:]' '[:upper:]'"
ollama profile add pretty "Return formatted JSON" --post-script "jq ."
ollama profile add deep "You are a thinking model" -m deepseek-r1:8b
ollama profile update python "You are a Python expert..." # Update profile
ollama profile update json -m gemma:7b-text # Change model
ollama profile delete work # Delete profile
EOF
}
help_model() {
cat << 'EOF'
Usage: ollama model <subcommand>
Manage and query available models.
Subcommands:
list List all available models with specs
get MODEL Show detailed model information
ps List currently running/loaded models
Examples:
ollama model list
ollama model get llama3:8b
ollama model get deepseek-r1:8b
ollama model ps
EOF
}
help_embed() {
cat << 'EOF'
Usage: ollama embed <text> [options]
Generate embeddings for text using a specified model. Supports reading from stdin.
Options:
-m, --model MODEL Use a specific model (default: llama3:8b)
-p, --profile NAME Use a profile's model selection (default: default)
-o, --output FORMAT Output format: text (default) or json
Output Formats:
text Display embedding summary with first 10 values
json Display full API response including all embedding values
Examples:
ollama embed "What is machine learning?"
ollama embed "Hello world" -m nomic-embed-text
echo "Some text to embed" | ollama embed
echo "Multiple lines
of text" | ollama embed -o json
ollama embed "Search query" -m nomic-embed-text -o json | jq '.embedding | length'
EOF
}
help_version() {
cat << 'EOF'
Usage: ollama version [options]
Display the CLI script version and the Ollama server version.
Options:
-o, --output FORMAT Output format: text (default) or json
Examples:
ollama version
ollama version -o json
ollama version -o json | jq '.ollama_version'
EOF
}
# Get version information
version() {
local output_format="$OUTPUT_FORMAT"
# Parse output format flag
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
output_format="$2"
shift 2
;;
*)
shift
;;
esac
done
local password
password=$(get_password)
# Get Ollama server version
local api_response
api_response=$(curl -s -u "$OLLAMA_USER:$password" \
-X GET "$OLLAMA_API_URL/version")
if ! echo "$api_response" | jq . &>/dev/null; then
echo "Error: Invalid response from API" >&2
return 1
fi
if echo "$api_response" | jq -e '.error' &>/dev/null; then
echo "Error: $(echo "$api_response" | jq -r '.error')" >&2
return 1
fi
local ollama_version
ollama_version=$(echo "$api_response" | jq -r '.version // "unknown"')
if [[ "$output_format" == "json" ]]; then
echo "$api_response" | jq --arg cli_version "$SCRIPT_VERSION" '{cli_version: $cli_version, ollama_version: .version}'
else
echo "ollama CLI version $SCRIPT_VERSION"
echo ""
echo "Ollama version $ollama_version"
fi
}
# Main
main() {
local command="${1:-help}"
case "$command" in
generate)
generate "${@:2}"
;;
embed)
embed "${@:2}"
;;
model|models)
model "${@:2}"
;;
chat)
chat "${@:2}"
;;
profile)
profile "${@:2}"
;;
version)
version "${@:2}"
;;
help|--help|-h)
if [[ $# -gt 1 ]]; then
local help_topic="$2"
case "$help_topic" in
generate)
help_generate
;;
embed)
help_embed
;;
chat)
help_chat
;;
profile)
help_profile
;;
model|models)
help_model
;;
version)
help_version
;;
*)
echo "Error: unknown command '$help_topic'" >&2
return 1
;;
esac
else
show_help
fi
;;
*)
echo "Error: unknown command '$command'" >&2
echo "Run 'ollama help' for usage information" >&2
return 1
;;
esac
}
main "$@"