Open closedLoop opened 1 month ago
Here's a claude rewrite of it. Lots of shellcheck bugs so I'm waiting to take a look at this
#!/bin/sh
# Autocomplete.sh - LLM Powered Zsh Completion
# Migrated from Bash version
# MIT License - ClosedLoop Technologies, Inc.
# Sean Kruzel 2024
export ACSH_VERSION=0.4.1
###############################################################################
#
# SUPPORTED MODELS AND MODEL PRICING
#
###############################################################################
typeset -A _autocomplete_modellist
# OpenAI models
_autocomplete_modellist['openai: gpt-4o']='{"completion_cost":0.0000150,"prompt_cost":0.00000500,"endpoint":"https://api.openai.com/v1/chat/completions","model":"gpt-4o","provider":"openai"}'
_autocomplete_modellist['openai: gpt-4o-mini']='{"completion_cost":0.0000006,"prompt_cost":0.00000015,"endpoint":"https://api.openai.com/v1/chat/completions","model":"gpt-4o-mini","provider":"openai"}'
_autocomplete_modellist['openai: gpt-3.5-turbo-0125']='{"completion_cost":0.0000015,"prompt_cost":0.00000050,"endpoint":"https://api.openai.com/v1/chat/completions","model":"gpt-3.5-turbo-0125","provider":"openai"}'
# Anthropic models
_autocomplete_modellist['anthropic: claude-3-5-sonnet-20240620']='{"completion_cost":0.0000150,"prompt_cost":0.0000030,"endpoint":"https://api.anthropic.com/v1/messages","model":"claude-3-5-sonnet-20240620","provider":"anthropic"}'
_autocomplete_modellist['anthropic: claude-3-opus-20240229']='{"completion_cost":0.0000750,"prompt_cost":0.0000150,"endpoint":"https://api.anthropic.com/v1/messages","model":"claude-3-opus-20240229","provider":"anthropic"}'
_autocomplete_modellist['anthropic: claude-3-haiku-20240307']='{"completion_cost":0.00000125,"prompt_cost":0.00000025,"endpoint":"https://api.anthropic.com/v1/messages","model":"claude-3-haiku-20240307","provider":"anthropic"}'
# Groq models
_autocomplete_modellist['groq: llama3-8b-8192']='{"completion_cost":0.0000000,"prompt_cost":0.0000000,"endpoint":"https://api.groq.com/openai/v1/chat/completions","model":"llama3-8b-8192","provider":"groq"}'
_autocomplete_modellist['groq: llama3-70b-8192']='{"completion_cost":0.0000000,"prompt_cost":0.0000000,"endpoint":"https://api.groq.com/openai/v1/chat/completions","model":"llama3-70b-8192","provider":"groq"}'
_autocomplete_modellist['groq: mixtral-8x7b-32768']='{"completion_cost":0.0000000,"prompt_cost":0.0000000,"endpoint":"https://api.groq.com/openai/v1/chat/completions","model":"mixtral-8x7b-32768","provider":"groq"}'
_autocomplete_modellist['groq: gemma-7b-it']='{"completion_cost":0.0000000,"prompt_cost":0.0000000,"endpoint":"https://api.groq.com/openai/v1/chat/completions","model":"gemma-7b-it","provider":"groq"}'
_autocomplete_modellist['groq: gemma2-9b-it']='{"completion_cost":0.0000000,"prompt_cost":0.0000000,"endpoint":"https://api.groq.com/openai/v1/chat/completions","model":"gemma2-9b-it","provider":"groq"}'
# Ollama models
_autocomplete_modellist['ollama: codellama']='{"completion_cost":0.0000000,"prompt_cost":0.0000000,"endpoint":"http://localhost:11434/api/chat","model":"codellama","provider":"ollama"}'
###############################################################################
#
# FORMATTING FUNCTIONS
#
###############################################################################
function echo_error() {
echo -e "\e[31mAutocomplete.sh - $1\e[0m" >&2
}
function echo_green() {
echo -e "\e[32m$1\e[0m"
}
###############################################################################
#
# SYSTEM INFORMATION FUNCTIONS
#
###############################################################################
# Check if jq is installed
if ! command -v jq &>/dev/null; then
echo_error "jq is not installed. Please install it using your package manager."
fi
function _get_terminal_info() {
local terminal_info=" * User name: \$USER=$USER
* Current directory: \$PWD=$PWD
* Previous directory: \$OLDPWD=$OLDPWD
* Home directory: \$HOME=$HOME
* Operating system: \$OSTYPE=$OSTYPE
* Shell: \$ZSH_VERSION=$ZSH_VERSION
* Terminal type: \$TERM=$TERM
* Hostname: \$HOST=$HOST
"
echo "$terminal_info"
}
# Generate a unique machine signature based on the hash of the uname and user
function machine_signature() {
local signature
signature=$(echo "$(uname -a)|$USER" | md5sum | cut -d ' ' -f 1)
echo "$signature"
}
function _system_info() {
echo "# System Information"
echo
uname -a
echo "SIGNATURE: $(machine_signature)"
echo
echo "ZSH_VERSION: $ZSH_VERSION"
echo
echo "## Terminal Information"
_get_terminal_info
}
function _completion_vars() {
echo "CURRENT: $CURRENT"
echo "words: ${words[*]}"
echo "BUFFER: $BUFFER"
echo "CURSOR: $CURSOR"
echo "LBUFFER: $LBUFFER"
echo "RBUFFER: $RBUFFER"
}
###############################################################################
#
# LARGE LANGUAGE MODEL COMPLETION FUNCTIONS
#
###############################################################################
function _get_system_message_prompt() {
echo "You are a helpful zsh_completion script. \
Generate relevant and concise auto-complete suggestion for the given user command \
in the context of the current directory, operating system, command history, \
and environment variables. \
The output must be a list of two to five possible completions or rewritten commands. \
Each command must be on a new line and must not span multiple lines. \
Each must be a valid command or set of commands. \
Please focus on the user's intent, recent commands, and the current environment when \
brainstorming completions. \
The output must not contain any backticks or quotes such as \`command\` or \"command\".
"
}
function _get_output_instructions() {
echo "Provide a list of suggested completions or commands that could be run in the terminal.
YOU MUST provide a list of two to five possible completions or rewritten commands here
DO NOT wrap the commands in backticks or quotes such as \`command\` or \"command\" or \`\`\`command\`\`\`
Each must be a valid command or set of commands somehow chained together that could be run in the terminal
Please focus on the user's intent, recent commands, and the current environment when brainstorming completions.
Take a deep breath. You got this!
RETURN A JSON OBJECT WITH THE COMPLETIONS"
}
function _get_command_history() {
local HISTORY_LIMIT
HISTORY_LIMIT=${ACSH_MAX_HISTORY_COMMANDS:-20}
fc -l -n -$HISTORY_LIMIT
}
# Find and replace sensitive information in the command history
function _get_clean_command_history() {
local recent_history
recent_history=$(_get_command_history)
recent_history=$(echo "$recent_history" | sed -E 's/:[[:xdigit:]]{36,40}/REDACTED_HASH/' )
recent_history="${recent_history//[A-Za-z0-9-]{36}/REDACTED_UUID}"
recent_history="${recent_history//[A-Za-z0-9]{16,40}/REDACTED_APIKEY}"
echo -e "$recent_history"
}
function _get_recent_files() {
local FILE_LIMIT
FILE_LIMIT=${ACSH_MAX_RECENT_FILES:-20}
find . -maxdepth 1 -type f -exec ls -ld {} + | sort -r | head -n "$FILE_LIMIT"
}
# Attempts to get the help message for a given command
function _get_help_message() {
local COMMAND HELP_INFO
COMMAND=$(echo "$1" | awk '{print $1}')
HELP_INFO=""
{
HELP_INFO=$(
cat <<EOF
$($COMMAND --help 2>&1 || true)
EOF
)
} || HELP_INFO="\`$COMMAND --help\` not available"
echo "$HELP_INFO"
}
# Constructs a LLM prompt with the user input and in-terminal contextual information
function _build_prompt() {
local user_input command_history terminal_context help_message recent_files output_instructions other_environment_variables prompt
user_input="$*"
command_history=$(_get_clean_command_history)
terminal_context=$(_get_terminal_info)
help_message=$(_get_help_message "$user_input")
recent_files=$(_get_recent_files)
output_instructions=$(_get_output_instructions)
other_environment_variables=$(env | grep '=' | grep -v 'ACSH_' | awk -F= '{print $1}' | grep -v 'PWD|OSTYPE|ZSH|USER|HOME|TERM|OLDPWD|HOST')
prompt="User command: \`$user_input\`
# Terminal Context
## Environment variables
$terminal_context
Other defined environment variables
\`\`\`
$other_environment_variables
\`\`\`
## History
Recently run commands (in order) - Note some information is REDACTED
\`\`\`
$command_history
\`\`\`
## File system
Most recently modified files in the current directory:
\`\`\`
$recent_files
\`\`\`
## Help Information
$help_message
# Instructions
$output_instructions
"
echo "$prompt"
}
# Constructs the payload for the API request
function _build_payload() {
local user_input prompt system_message_prompt payload acsh_prompt
local model temperature
model="${ACSH_MODEL:-"gpt-4o"}"
temperature=${ACSH_TEMPERATURE:-0.0}
user_input="$1"
prompt=$(_build_prompt "$@")
system_message_prompt=$(_get_system_message_prompt)
# EXPORT PROMPT TO #ACSH_PROMPT
acsh_prompt="# SYSTEM PROMPT\n"
acsh_prompt+=$system_message_prompt
acsh_prompt+="\n# USER MESSAGE\n"
acsh_prompt+=$prompt
export ACSH_PROMPT=$acsh_prompt
case ${ACSH_PROVIDER:l} in
anthropic)
payload=$(jq -cn --arg model "$model" --argjson temperature "$temperature" --arg system_prompt "$system_message_prompt" --arg prompt_content "$prompt" '{
model: $model,
system: $system_prompt,
messages: [
{role: "user", content: $prompt_content}
],
temperature: $temperature,
"max_tokens": 1024,
tool_choice: {"type": "tool", "name": "zsh_completions"},
tools:[
{
"name": "zsh_completions",
"description": "syntactically correct command-line suggestions based on the users input",
"input_schema": {
"type": "object",
"properties": {
"commands": {
"type": "array",
"items": {
"type": "string",
"description": "A suggested command"
}
},
},
"required": ["commands"],
},
}
]
}')
;;
groq)
payload=$(jq -cn --arg model "$model" --argjson temperature "$temperature" --arg system_prompt "$system_message_prompt" --arg prompt_content "$prompt" '{
model: $model,
messages: [
{role: "system", content: $system_prompt},
{role: "user", content: $prompt_content}
],
temperature: $temperature,
response_format: { "type": "json_object" }
}')
;;
ollama)
payload=$(jq -cn --arg model "$model" --argjson temperature "$temperature" --arg system_prompt "$system_message_prompt" --arg prompt_content "$prompt" '{
model: $model,
system: $system_prompt,
messages: [
{role: "user", content: $prompt_content}
],
"format": "json",
stream: false,
"options": {
"temperature": $temperature,
}
}')
;;
*)
payload=$(jq -cn --arg model "$model" --argjson temperature "$temperature" --arg system_prompt "$system_message_prompt" --arg prompt_content "$prompt" '{
model: $model,
messages: [
{role: "system", content: $system_prompt},
{role: "user", content: $prompt_content}
],
temperature: $temperature,
response_format: { "type": "json_object" },
tool_choice: {"type": "function", "function": {"name": "zsh_completions"}},
tools:[
{
"type": "function",
"function": {
"name": "zsh_completions",
"description": "syntactically correct command-line suggestions based on the users input",
"parameters": {
"type": "object",
"properties": {
"commands": {
"type": "array",
"items": {
"type": "string",
"description": "A suggested command"
}
},
},
"required": ["commands"],
},
},
}
]
}')
;;
esac
echo "$payload"
}
function log_request() {
local user_input response_body user_input_hash log_file
local prompt_tokens prompt_tokens_int completion_tokens completion_tokens_int created api_cost
user_input="$1"
response_body="$2"
user_input_hash=$(echo -n "$user_input" | md5sum | cut -d ' ' -f 1)
if [[ "${ACSH_PROVIDER:l}" == "anthropic" ]]; then
prompt_tokens=$(echo "$response_body" | jq -r '.usage.input_tokens')
prompt_tokens_int=$((prompt_tokens))
completion_tokens=$(echo "$response_body" | jq -r '.usage.output_tokens')
completion_tokens_int=$((completion_tokens))
else
prompt_tokens=$(echo "$response_body" | jq -r '.usage.prompt_tokens')
prompt_tokens_int=$((prompt_tokens))
completion_tokens=$(echo "$response_body" | jq -r '.usage.completion_tokens')
completion_tokens_int=$((completion_tokens))
fi
created=$(date +%s)
created=$(echo "$response_body" | jq -r ".created // $created")
api_cost=$(echo "$prompt_tokens_int * $ACSH_API_PROMPT_COST + $completion_tokens_int * $ACSH_API_COMPLETION_COST" | bc)
log_file=${ACSH_LOG_FILE:-"$HOME/.autocomplete/autocomplete.log"}
echo "$created,$user_input_hash,$prompt_tokens_int,$completion_tokens_int,$api_cost" >> "$log_file"
}
function openai_completion() {
local content status_code response_body default_user_input user_input
local api_key config_file payload response completions endpoint timeout
local prompt_tokens completion_tokens created api_cost prompt_tokens_int completion_tokens_int
endpoint=${ACSH_ENDPOINT:-"https://api.openai.com/v1/chat/completions"}
timeout=${ACSH_TIMEOUT:-30}
default_user_input="Write two to six most likely commands given the provided information"
user_input=${*:-$default_user_input}
if [[ -z "$ACSH_ACTIVE_API_KEY" && ${ACSH_PROVIDER:u} != "OLLAMA" ]]; then
echo ""
echo_error "ACSH_ACTIVE_API_KEY not set"
echo -e "Please set it using the following command: \e[90mexport ${ACSH_PROVIDER:u}_API_KEY=<your-api-key>\e[0m"
echo -e "or set it in the ~/.autocomplete/config configuration file via: \e[90mautocomplete config set ${ACSH_PROVIDER:u}_API_KEY <your-api-key>\e[0m"
return
fi
api_key="${ACSH_ACTIVE_API_KEY:-$OPENAI_API_KEY}"
payload=$(_build_payload "$user_input")
case "${ACSH_PROVIDER:l}" in
anthropic)
response=$(curl -s -m "$timeout" -w "\n%{http_code}" "$endpoint" \
-H "content-type: application/json" \
-H "anthropic-version: 2023-06-01" \
-H "x-api-key: $api_key" \
--data "$payload")
;;
ollama)
response=$(curl -s -m "$timeout" -w "\n%{http_code}" "$endpoint" \
--data "$payload")
;;
*)
response=$(curl -s -m "$timeout" -w "%{http_code}" "$endpoint" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $api_key" \
-d "$payload")
;;
esac
status_code=$(echo "$response" | tail -n1)
response_body=$(echo "$response" | sed '$d')
if [[ $status_code -eq 200 ]]; then
case "${ACSH_PROVIDER:l}" in
anthropic)
content=$(echo "$response_body" | jq -r '.content[0].input.commands')
;;
groq)
content=$(echo "$response_body" | jq -r '.choices[0].message.content')
content=$(echo "$content" | jq -r '.completions')
;;
ollama)
content=$(echo "$response_body" | jq -r '.message.content')
content=$(echo "$content" | jq -r '.completions')
;;
*)
content=$(echo "$response_body" | jq -r '.choices[0].message.tool_calls[0].function.arguments')
content=$(echo "$content" | jq -r '.commands')
;;
esac
completions=$(echo "$content" | jq -r '.[]' | grep -v '^$')
echo -n "$completions"
log_request "$user_input" "$response_body"
else
echo
case $status_code in
400)
echo_error "Bad Request: The API request was invalid or malformed."
;;
401)
echo_error "Unauthorized: The provided API key is invalid or missing."
;;
429)
echo_error "Too Many Requests: The API rate limit has been exceeded."
;;
500)
echo_error "Internal Server Error: An unexpected error occurred on the API server."
;;
*)
echo_error "Unknown Error: Unexpected status code $status_code received from the API - $response_body \n$payload"
;;
esac
fi
}
###############################################################################
#
# Completion Functions
#
###############################################################################
function _get_default_completion_function() {
local cmd="$1"
complete -p "$cmd" 2>/dev/null | awk -F' ' '{ for(i=1;i<=NF;i++) { if ($i ~ /^-F$/) { print $(i+1); exit; } } }'
}
function _default_completion() {
local current_word=""
local first_word=""
local default_func
if [[ -n "${words[*]}" ]]; then
first_word="${words[1]}"
if [[ -n "$CURRENT" && "$CURRENT" -le "${#words[@]}" ]]; then
current_word="${words[CURRENT]}"
fi
fi
default_func=$(_get_default_completion_function "$first_word")
if [[ -n "$default_func" ]]; then
$default_func
else
local file_completions
if [[ -z "$current_word" ]]; then
file_completions=($(compgen -f -- || true))
else
file_completions=($(compgen -f -- "$current_word" || true))
fi
if [[ -n "$file_completions" ]]; then
compadd -a file_completions
fi
fi
}
function list_cache() {
local cache_dir cache_files
cache_dir=${ACSH_CACHE_DIR:-"$HOME/.autocomplete/cache"}
cache_files=$(find "$cache_dir" -maxdepth 1 -type f -name "acsh-*" -printf '%T+ %p\n' | sort)
echo "$cache_files"
}
function _autocompletezsh() {
local curcontext="$curcontext" state line
typeset -A opt_args
_arguments \
'1: :->command' \
'*: :->args'
case $state in
command)
local -a commands
commands=(
'install:Install the autocomplete script'
'remove:Remove the autocomplete script'
'config:Display or set configuration'
'enable:Enable the autocomplete script'
'disable:Disable the autocomplete script'
'clear:Clear the cache and log'
'usage:Display usage information'
'system:Display system information'
'command:Run autocomplete command'
'model:Select language model'
)
_describe -t commands 'autocomplete commands' commands
;;
args)
case $words[1] in
config)
_arguments '2:action:(set reset)'
;;
command)
_arguments '--dry-run[Show prompt without running]'
;;
esac
;;
esac
if [[ ${#compadd_args[@]} -eq 0 && $CURRENT -gt 1 ]]; then
local completions
completions=$(openai_completion "${words[*]}")
if [[ -n "$completions" ]]; then
_values 'completions' ${(f)completions}
fi
fi
}
###############################################################################
#
# CLI ENTRY POINT
#
###############################################################################
function show_help() {
echo_green "Autocomplete.sh - LLM Powered Zsh Completion"
echo "Usage: autocomplete [options] command"
echo " autocomplete [options] install|remove|config|model|enable|disable|clear|usage|system|command|--help"
echo
echo "autocomplete.sh is a script to enhance zsh completion with LLM capabilities."
echo
echo "Once installed and enabled, it will provide suggestions for the current command."
echo "Just by pressing the Tab key, you can get the most likely completion for the command."
echo "It provides various commands to manage and configure the autocomplete features."
echo
echo "Most used:"
echo " command Run the autocomplete command same as pressing <tab><tab>"
echo " command --dry-run Only show the prompt without running the command"
echo
echo "Configuration and Settings:"
echo " model Change the language model used for completion"
echo " usage Display usage information including cost"
echo " system Displays system information"
echo " config Displays status and config values"
echo " config set <key> <value> Set a configuration value"
echo " config reset Reset configuration to default values"
echo
echo "Installation / Removal / Cache Reset:"
echo " install Install the autocomplete script from .zshrc"
echo " remove Remove the autocomplete script from .zshrc"
echo " enable Enable the autocomplete script"
echo " disable Disable the autocomplete script"
echo " clear Clear the cache directory and log file"
echo
echo "Submit bugs or feedback here: https://github.com/closedloop-technologies/autocomplete-sh/issues"
echo "For more information, visit: https://autocomplete.sh"
}
# Function to check if running in a subshell
function is_subshell() {
if [[ $$ != $PPID ]]; then
return 0
else
return 1
fi
}
function show_config() {
local config_file config_value
local term_width small_table rest
echo_green "Autocomplete.sh - Configuration and Settings - Version $ACSH_VERSION"
if is_subshell; then
echo -e " STATUS: \033[33;5mUnknown\033[0m \033[0m"
echo -e " Run \033[33;5msource autocomplete config\033[0m to see if autocomplete is enabled"
return
elif check_if_enabled; then
echo -e " STATUS: \033[32;5mEnabled\033[0m \033[0m"
else
echo -e " STATUS: \033[31;5mDisabled\033[0m \033[0m- WARNING might be a false negative. Make sure to"
echo -e " run \033[33;5msource autocomplete config\033[0m to confirm if autocomplete disabled"
fi
config_file="$HOME/.autocomplete/config"
if [ ! -f "$config_file" ]; then
echo_error "Configuration file not found: $config_file"
echo_error "Run autocomplete install"
return
fi
acsh_load_config
echo
term_width=$(tput cols)
if [[ term_width -gt 70 ]]; then
term_width=70
small_table=0
fi
if [[ term_width -lt 40 ]]; then
term_width=70
small_table=1
fi
for config_var in ${(k)parameters[(R)*ACSH_*]}; do
if [[ $config_var == "ACSH_INPUT" ]] || [[ $config_var == "ACSH_PROMPT" ]] || [[ $config_var == "ACSH_RESPONSE" ]]; then
continue
fi
config_value="${(P)config_var}"
if [[ ${config_var: -8} == "_API_KEY" ]]; then
continue
fi
echo -en " $config_var:\e[90m"
if [[ small_table -eq 1 ]]; then
echo -e "\n $config_value\e[0m"
else
printf '%s%*s' "" $((term_width - ${#config_var} - ${#config_value} - 3)) ''
echo -e "$config_value\e[0m"
fi
done
echo -e " ===================================================================="
for config_var in ${(k)parameters[(R)*ACSH_*]}; do
if [[ $config_var == "ACSH_INPUT" ]] || [[ $config_var == "ACSH_PROMPT" ]] || [[ $config_var == "ACSH_RESPONSE" ]]; then
continue
fi
config_value="${(P)config_var}"
if [[ ${config_var: -8} != "_API_KEY" ]]; then
continue
fi
echo -en " $config_var:\e[90m"
if [[ -z ${(P)config_var} ]]; then
echo -en "\e[31m"
config_value="UNSET"
else
echo -en "\e[32m"
rest=${(P)config_var:4}
config_value="${(P)config_var:0:4}...${rest: -4}"
fi
if [[ small_table -eq 1 ]]; then
echo -e "\n $config_value\e[0m"
else
printf '%s%*s' "" $((term_width - ${#config_var} - ${#config_value} - 3)) ''
echo -e "$config_value\e[0m"
fi
done
}
function set_config() {
local key="$1"
local value="$2"
local config_file="$HOME/.autocomplete/config"
key=${key:l}
key=${key//[^a-zA-Z0-9]/_}
if [ -z "$key" ]; then
echo_error "SyntaxError: expected \`autocomplete config set <key> <value>\`"
return
fi
if [ ! -f "$config_file" ]; then
echo_error "Configuration file not found: $config_file"
echo_error "Run autocomplete install"
return
fi
sed -i "s|^\($key:\).*|\1 $value|" "$config_file"
acsh_load_config
}
function config_command() {
local command config_file
config_file="$HOME/.autocomplete/config"
command="${*:2}"
if [ -z "$command" ]; then
show_config
return
fi
if [ "$2" = "set" ]; then
local key="$3"
local value="$4"
echo -e "Setting configuration key \`$key\` to value \`$value\`"
set_config "$key" "$value"
echo_green "Configuration updated: run \`autocomplete config\` to see the changes"
return
fi
if [[ "$command" == "reset" ]]; then
echo "Resetting configuration to default values"
rm "$config_file" || true
build_config
return
fi
echo_error "SyntaxError: expected \`autocomplete config set <key> <value>\ or autocomplete config reset\`"
}
function build_config() {
local config_file default_config api_key
config_file="$HOME/.autocomplete/config"
if [ ! -f "$config_file" ]; then
echo "Creating the ~/.autocomplete/config file with default values"
api_key="${ACSH_ACTIVE_API_KEY:-$OPENAI_API_KEY}"
default_config="# ~/.autocomplete/config
# OpenAI API Key set here or as an environment variable named OPENAI_API_KEY
# https://platform.openai.com/api-keys
openai_api_key: $OPENAI_API_KEY
# Anthropic/Claude API Key set here or as an environment variable ANTHROPIC_API_KEY
# https://console.anthropic.com/settings/keys
anthropic_api_key: $ANTHROPIC_API_KEY
# Groq API Key set here or as en environment variable GROQ_API_KEY
# https://console.groq.com/keys
groq_api_key: $GROQ_API_KEY
# If your custom deployment like Ollama needs a key set it here or LLM_API_KEY
custom_api_key: $LLM_API_KEY
# Model configuration
## Model List https://platform.openai.com/docs/models
provider: openai
model: gpt-4o
temperature: 0.0
endpoint: https://api.openai.com/v1/chat/completions
# If pricing is outdated, submit issue: https://github.com/closedloop-technologies/autocomplete-sh/issues
# Sources: https://openai.com/api/pricing/
api_prompt_cost: 0.000005
api_completion_cost: 0.000015
# Max number of history commands and recent files to include in the prompt
max_history_commands: 20
max_recent_files: 20
# Cache settings
cache_dir: $HOME/.autocomplete/cache
cache_size: 10
# Logging settings
log_file: $HOME/.autocomplete/autocomplete.log"
echo "$default_config" > "$config_file"
fi
}
function acsh_load_config() {
local config_file key value
config_file="$HOME/.autocomplete/config"
if [ -f "$config_file" ]; then
while IFS=':' read -r key value; do
if [[ $key == \#* ]] || [[ -z $key ]]; then
continue
fi
key=$(echo "$key" | tr -d '[:space:]')
value=$(echo "$value" | tr -d '[:space:]')
key=${key:u}
key=${key//[^[:alnum:]]/_}
if [[ -n $value ]]; then
export "ACSH_$key"="$value"
fi
done < "$config_file"
if [[ -z "$ACSH_OPENAI_API_KEY" && -n "$OPENAI_API_KEY" ]]; then
export ACSH_OPENAI_API_KEY="$OPENAI_API_KEY"
fi
if [[ -z "$ACSH_ANTHROPIC_API_KEY" && -n "$ANTHROPIC_API_KEY" ]]; then
export ACSH_ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY"
fi
if [[ -z "$ACSH_GROQ_API_KEY" && -n "$GROQ_API_KEY" ]]; then
export ACSH_GROQ_API_KEY="$GROQ_API_KEY"
fi
if [[ -z "$ACSH_CUSTOM_API_KEY" && -n "$LLM_API_KEY" ]]; then
export ACSH_CUSTOM_API_KEY="$LLM_API_KEY"
fi
case "${ACSH_PROVIDER:-openai}" in
"openai")
export ACSH_ACTIVE_API_KEY="$ACSH_OPENAI_API_KEY"
;;
"anthropic")
export ACSH_ACTIVE_API_KEY="$ACSH_ANTHROPIC_API_KEY"
;;
"groq")
export ACSH_ACTIVE_API_KEY="$ACSH_GROQ_API_KEY"
;;
"ollama")
export ACSH_ACTIVE_API_KEY="$ACSH_OLLAMA_API_KEY"
;;
*)
echo_error "Unknown provider: $ACSH_PROVIDER"
;;
esac
else
echo "Configuration file not found: $config_file"
fi
}
function install_command() {
local zshrc_file autocomplete_setup autocomplete_cli_setup
zshrc_file="$HOME/.zshrc"
autocomplete_setup="source autocomplete enable"
autocomplete_cli_setup="compdef _autocompletezsh autocomplete"
if ! command -v autocomplete &>/dev/null; then
echo_error "autocomplete.sh is not in the PATH
Please follow the install instructions on https://github.com/closedloop-technologies/autocomplete-sh"
return
fi
if [[ ! -d "$HOME/.autocomplete" ]]; then
echo "Creating the ~/.autocomplete directory"
mkdir -p "$HOME/.autocomplete"
fi
local cache_dir=${ACSH_CACHE_DIR:-"$HOME/.autocomplete/cache"}
if [[ ! -d "$cache_dir" ]]; then
mkdir -p "$cache_dir"
fi
build_config
acsh_load_config
if ! grep -qF "$autocomplete_setup" "$zshrc_file"; then
echo -e "# Autocomplete.sh" >> "$zshrc_file"
echo -e "$autocomplete_setup\n" >> "$zshrc_file"
echo "Added autocomplete.sh setup to $zshrc_file"
else
echo "Autocomplete.sh setup already exists in $zshrc_file"
fi
if ! grep -qF "$autocomplete_cli_setup" "$zshrc_file"; then
echo -e "# Autocomplete.sh CLI" >> "$zshrc_file"
echo -e "$autocomplete_cli_setup\n" >> "$zshrc_file"
echo "Added autocomplete cli autocomplete to $zshrc_file"
fi
echo
echo_green "Autocomplete.sh - LLM Powered Zsh Completion - Version $ACSH_VERSION"
echo -e "\e[1;34mInstall Completed\e[0m"
echo
echo "Two more steps to complete the installation:"
echo -e "1. $ \e[1;34msource $zshrc_file\e[0m to enable autocomplete"
echo -e "2. $ \e[1;34mautocomplete model\e[0m \tto select the language model"
echo
echo -e "$ \e[1;30mautocomplete config\e[0m \tto view settings"
echo -e "$ \e[1;30mautocomplete --help\e[0m \tfor additional commands"
echo
echo -e "Visit \e[1;32mhttps://autocomplete.sh\e[0m for examples and more information"
echo
}
function remove_command() {
local config_file cache_dir log_file zshrc_file
config_file="$HOME/.autocomplete/config"
cache_dir=${ACSH_CACHE_DIR:-"$HOME/.autocomplete/cache"}
log_file=${ACSH_LOG_FILE:-"$HOME/.autocomplete/autocomplete.log"}
zshrc_file="$HOME/.zshrc"
echo_green "Autocomplete.sh - Removing files, directories, and zshrc setup..."
if [ -f "$config_file" ]; then
rm "$config_file"
echo "Removed: $config_file"
fi
if [ -d "$cache_dir" ]; then
rm -rf "$cache_dir"
echo "Removed: $cache_dir"
fi
if [ -f "$log_file" ]; then
rm "$log_file"
echo "Removed: $log_file"
fi
if [ -d "$HOME/.autocomplete" ]; then
if [ -z "$(ls -A "$HOME/.autocomplete")" ]; then
rmdir "$HOME/.autocomplete"
echo "Removed: $HOME/.autocomplete"
else
echo "Skipped removing $HOME/.autocomplete (directory not empty)"
fi
fi
if [ -f "$zshrc_file" ]; then
if grep -qF "source autocomplete enable" "$zshrc_file"; then
sed -i '/# Autocomplete.sh/d' "$zshrc_file"
sed -i '/autocomplete/d' "$zshrc_file"
echo "Removed autocomplete.sh setup from $zshrc_file"
fi
fi
local autocomplete_script
autocomplete_script=$(command -v autocomplete)
if [ -n "$autocomplete_script" ]; then
echo "The autocomplete script is located at: $autocomplete_script"
if [ "$1" = "-y" ]; then
rm "$autocomplete_script"
echo "Removed: $autocomplete_script"
else
read -r "confirm?Do you want to remove the autocomplete script? (y/n): "
if [[ $confirm == "y" ]]; then
rm "$autocomplete_script"
echo "Removed: $autocomplete_script"
fi
fi
fi
echo "Completed uninstalling autocomplete.sh"
}
function check_if_enabled() {
local is_enabled
is_enabled=$(complete -p | grep _autocompletezsh | grep -cv _autocompletezsh_cli)
if [ "$is_enabled" -gt 0 ]; then
return 0
else
return 1
fi
}
function enable_command() {
if check_if_enabled; then
echo_green "Autocomplete.sh - reloading"
disable_command
fi
acsh_load_config
compdef _autocompletezsh -D -E
}
function disable_command() {
if check_if_enabled; then
uncompdef -D
fi
}
function command_command() {
local args=("$@")
for ((i = 1; i <= ${#args[@]}; i++)); do
if [ "${args[i]}" = "--dry-run" ]; then
args[i]=()
_build_prompt "${args[@]}"
return
fi
done
openai_completion "$@" || true
echo
}
function clear_command() {
local cache_dir log_file
acsh_load_config
cache_dir=${ACSH_CACHE_DIR:-"$HOME/.autocomplete/cache"}
log_file=${ACSH_LOG_FILE:-"$HOME/.autocomplete/autocomplete.log"}
echo "This will remove the cache directory and log file"
echo -e "Cache dir:\t\e[31m$cache_dir\e[0m"
echo -e "Log file:\t\e[31m$log_file\e[0m"
read -r "confirm?Are you sure you want to continue? (y/n): "
if [[ $confirm != "y" ]]; then
echo "Aborted"
return
fi
if [ -d "$cache_dir" ]; then
cache_files=$(list_cache)
echo "$cache_file"
if [ -n "$cache_files" ]; then
for last_update_and_filename in $cache_files; do
file=$(echo "$last_update_and_filename" | cut -d ' ' -f 2)
rm "$file"
echo "Removed: $file"
done
echo "Removed files in: $cache_dir"
else
echo "Cache directory is empty"
fi
echo "Removed: $cache_dir"
fi
if [ -f "$log_file" ]; then
rm "$log_file"
echo "Removed: $log_file"
fi
}
function usage_command() {
local log_file number_of_lines api_cost cache_dir
log_file=${ACSH_LOG_FILE:-"$HOME/.autocomplete/autocomplete.log"}
cache_dir=${ACSH_CACHE_DIR:-"$HOME/.autocomplete/cache"}
cache_size=$(list_cache | wc -l)
echo_green "Autocomplete.sh - Usage Information"
echo
echo -n "Log file: "
echo -en "\e[90m"
echo "$log_file"
echo -en "\e[0m"
if [ ! -f "$log_file" ]; then
number_of_lines=0
api_cost=0
avg_api_cost=0
else
number_of_lines=$(wc -l < "$log_file")
api_cost=$(awk -F, '{sum += $5} END {print sum}' "$log_file")
avg_api_cost=$(echo "$api_cost / $number_of_lines" | bc -l)
fi
echo
echo -e "API Calls:\t"
if [[ $number_of_lines -eq 0 ]]; then
echo_error "No usage data found"
return
else
earliest_date=$(awk -F, '{print $1}' "$log_file" | sort | head -n 1)
if [[ -n "$earliest_date" ]]; then
earliest_date=$(date -d@"$earliest_date")
echo -en "\e[90m"
echo "Since $earliest_date"
echo -en "\e[0m"
fi
fi
echo
echo -en "\tUsage count:\t"
echo -en "\e[32m"
printf "%9s\n" "$number_of_lines"
echo -en "\e[0m"
echo -en "\tAvg Cost:\t$"
printf "%8.4f\n" "$avg_api_cost"
echo -e "\e[90m\t-------------------------\e[0m"
echo -en "\tTotal Cost:\t$"
echo -en "\e[31m"
printf "%8.4f\n" "$api_cost"
echo -en "\e[0m"
echo
echo
echo -n "Cache Size: ${cache_size} of ${ACSH_CACHE_SIZE:-10} in "
echo -e "\e[90m$cache_dir\e[0m"
echo
echo -e "To clear the log file and cache directory, run: \e[90mautocomplete clear\e[0m"
}
function get_key() {
local key
read -sk1 key
case $key in
$'\x1b')
read -sk2 key
case $key in
'[A') echo up ;;
'[B') echo down ;;
esac
;;
'') echo enter ;;
esac
}
function menu_selector() {
local -a options
options=("$@")
local selected=1
function show_menu() {
echo
echo "Select a Language Model:"
echo
for i in {1..${#options[@]}}; do
if [[ $i -eq $selected ]]; then
echo -e "\e[1;32m> ${options[i]}\e[0m"
else
echo -e " ${options[i]}"
fi
done
echo
echo -ne "Press Enter to select the model\t"
}
tput sc
while true; do
tput rc
tput ed
show_menu
key=$(get_key)
case $key in
up)
((selected--))
if ((selected < 1)); then
selected=${#options[@]}
fi
;;
down)
((selected++))
if ((selected > ${#options[@]})); then
selected=1
fi
;;
enter)
break
;;
esac
done
clear
return $((selected - 1))
}
function model_command() {
clear
local selected_model options=()
if [[ $# -ne 3 ]]; then
local -a sorted_keys
sorted_keys=(${(onk)_autocomplete_modellist})
for key in $sorted_keys; do
options+=("$key")
done
echo -e "\e[1;32mAutocomplete.sh - Model Configuration\e[0m"
menu_selector "${options[@]}"
selected_option=$?
echo -e "\e[1;32mAutocomplete.sh - Model Configuration\e[0m"
selected_model="${options[selected_option+1]}"
selected_value="${_autocomplete_modellist[$selected_model]}"
else
local provider="$2"
local model_name="$3"
selected_value="${_autocomplete_modellist["$provider: $model_name"]}"
if [[ -z "$selected_value" ]]; then
echo "ERROR: Invalid provider or model name."
return 1
fi
fi
set_config "model" "$(echo "$selected_value" | jq -r '.model')"
set_config "endpoint" "$(echo "$selected_value" | jq -r '.endpoint')"
set_config "provider" "$(echo "$selected_value" | jq -r '.provider')"
local prompt_cost=$(echo "$selected_value" | jq -r '.prompt_cost' | awk '{printf "%.8f", $1}' )
local completion_cost=$(echo "$selected_value" | jq -r '.completion_cost' | awk '{printf "%.8f", $1}')
set_config "api_prompt_cost" "$prompt_cost"
set_config "api_completion_cost" "$completion_cost"
if [[ -z "$ACSH_ACTIVE_API_KEY" && ${ACSH_PROVIDER:u} != "OLLAMA" ]]; then
echo -e "\e[34mSet ${ACSH_PROVIDER:u}_API_KEY\e[0m"
echo
echo -e "This is stored locally here: \e[90m~/.autocomplete/config\e[0m"
echo
case ${ACSH_PROVIDER:u} in
OPENAI)
echo "Create a new one here: https://platform.openai.com/settings/profile?tab=api-keys"
;;
ANTHROPIC)
echo "Create a new one here: https://console.anthropic.com/settings/keys"
;;
GROQ)
echo "Create a new one here: https://console.groq.com/keys"
;;
esac
echo
echo -n "Enter your ${ACSH_PROVIDER:u} API Key: "
read -rs user_api_key_input
clear
echo -e "\e[1;32mAutocomplete.sh - Model Configuration\e[0m"
if [[ -n "$user_api_key_input" ]]; then
export ACSH_ACTIVE_API_KEY="$user_api_key_input"
set_config "${ACSH_PROVIDER:l}_api_key" "$user_api_key_input"
fi
fi
local model="${ACSH_MODEL:-"ERROR"}"
local temperature=$(echo "${ACSH_TEMPERATURE:-0.0}" | awk '{printf "%.3f", $1}' )
echo -e "Provider:\t\e[90m$ACSH_PROVIDER\e[0m"
echo -e "Model:\t\t\e[90m$model\e[0m"
echo -e "Temperature:\t\e[90m$temperature\e[0m"
echo
echo -e "Cost/token:\t\e[90mprompt: \t\$$ACSH_API_PROMPT_COST\e[0m"
echo -e " \t\e[90mcompletion:\t\$$ACSH_API_COMPLETION_COST\e[0m"
echo
echo -e "Endpoint: \t\e[90m$ACSH_ENDPOINT\e[0m"
echo -en "API Key: \t"
if [[ -z $ACSH_ACTIVE_API_KEY ]]; then
if [[ ${ACSH_PROVIDER:u} == "OLLAMA" ]]; then
echo -e "\e[90mNot Used\e[0m"
else
echo -e "\e[31mUNSET"
fi
else
local rest=${ACSH_ACTIVE_API_KEY:4}
local config_value="${ACSH_ACTIVE_API_KEY:0:4}...${rest: -4}"
echo -en "\e[32m${config_value}"
fi
echo -en "\e[0m"
if [[ -z $ACSH_ACTIVE_API_KEY && ${ACSH_PROVIDER:u} != "OLLAMA" ]]; then
echo
echo "To set the API Key, run either:"
echo -e "\t\e[31mautocomplete config set api_key <your-api-key>\e[0m"
echo -e "\t\e[31mexport ${ACSH_PROVIDER:u}_API_KEY=<your-api-key>\e[0m"
else
echo
fi
if [[ ${ACSH_PROVIDER:u} == "OLLAMA" ]]; then
echo "To set a custom endpoint:"
echo -e "\t\e[34mautocomplete config set endpoint <your-url>\e[0m"
echo "Other models can be set:"
echo -e "\t\e[34mautocomplete config set model <model-name>\e[0m"
fi
echo "To change the temperature:"
echo -e "\t\e[90mautocomplete config set temperature <temperature>\e[0m"
echo
}
# Main script logic
case "$1" in
--help)
show_help
;;
system)
_system_info
;;
install)
install_command
;;
remove)
remove_command "$@"
;;
clear)
clear_command
;;
usage)
usage_command
;;
model)
model_command "$@"
;;
config)
config_command "$@"
;;
enable)
enable_command
;;
disable)
disable_command
;;
command)
command_command "$@"
;;
*)
if [[ -n "$1" ]]; then
echo_error "Unknown command $1 - run \`autocomplete --help\` or goto https://autocomplete.sh for more information"
else
echo_green "Autocomplete.sh - LLM Powered Zsh Completion - Version $ACSH_VERSION - https://autocomplete.sh"
fi
;;
esac
# Enable completion for the autocomplete command itself
compdef _autocompletezsh autocomplete
@closedLoop running in zsh
userland over here, so I may be able to help, a bit.
On OSX, one of the friction points I'm seeing with v0.4.1
is when I copy/paste the install.sh
, I get the following error:
wget -qO- https://autocomplete.sh/install.sh | bash 2024-08-04 10:55:51 URL:https://raw.githubusercontent.com/closedloop-technologies/autocomplete- sh/v0.4.1/autocomplete.sh [51027/51027] -> "/Users/localhost/.local/bin/autocomplete" [1] ERROR: Please ensure you have bash-completion installed and sourced.
[I belive/hope] Relevant lines of .zshrc
:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
As well, when I run autocomplete
directly from zsh
, I get this output:
/Users/localhost/.local/bin/autocomplete: line 20: declare: -A: invalid option
declare: usage: declare [-afFirtx] [-p] [name[=value] ...]
/Users/localhost/.local/bin/autocomplete: line 23: openai:\tgpt-4o: syntax error in expression (error token is ":\tgpt-4o")
/Users/localhost/.local/bin/autocomplete: line 24: openai:\tgpt-4o-mini: syntax error in expression (error token is ":\tgpt-4o-mini")
/Users/localhost/.local/bin/autocomplete: line 25: openai:\tgpt-3.5-turbo-0125: syntax error in expression (error token is ":\tgpt-3.5-turbo-0125")
/Users/localhost/.local/bin/autocomplete: line 27: anthropic:\tclaude-3-5-sonnet-20240620: syntax error in expression (error token is ":\tclaude-3-5-sonnet-20240620")
/Users/localhost/.local/bin/autocomplete: line 28: anthropic:\tclaude-3-opus-20240229: syntax error in expression (error token is ":\tclaude-3-opus-20240229")
/Users/localhost/.local/bin/autocomplete: line 29: anthropic:\tclaude-3-haiku-20240307: syntax error in expression (error token is ":\tclaude-3-haiku-20240307")
/Users/localhost/.local/bin/autocomplete: line 32: groq:\t\tllama3-8b-8192: syntax error in expression (error token is ":\t\tllama3-8b-8192")
/Users/localhost/.local/bin/autocomplete: line 33: groq:\t\tllama3-70b-8192: syntax error in expression (error token is ":\t\tllama3-70b-8192")
/Users/localhost/.local/bin/autocomplete: line 34: groq:\t\tmixtral-8x7b-32768: syntax error in expression (error token is ":\t\tmixtral-8x7b-32768")
/Users/localhost/.local/bin/autocomplete: line 35: groq:\t\tgemma-7b-it: syntax error in expression (error token is ":\t\tgemma-7b-it")
/Users/localhost/.local/bin/autocomplete: line 36: groq:\t\tgemma2-9b-it: syntax error in expression (error token is ":\t\tgemma2-9b-it")
/Users/localhost/.local/bin/autocomplete: line 39: ollama:\tcodellama: syntax error in expression (error token is ":\tcodellama")
\e[32mAutocomplete.sh - LLM Powered Bash Completion - Version 0.4.0 - https://autocomplete.sh\e[0m
Which suggests that I may have an older version configured/already installed, and need to make updates to my zshrc
for the upgrade?
I'm also interested in zsh support, so let mem bump this topic.
Bump! Also interested.
So I'd love to support zsh as well as bash
From Claude: Yes, there are several key differences between how Bash and Zsh implement command-line completions. Here are some of the main points to consider when migrating a bash_completion script to Zsh:
Syntax:
complete
command for defining completions.compdef
function and the#compdef
special comment.Completion functions:
complete -F
to associate it with a command._commandname
) and usecompdef
to associate it.Context awareness:
Completion specification:
Built-in completion functions:
Performance:
Customization:
Caching:
When migrating your script, you'll need to rewrite your completion logic using Zsh's completion system. This often involves using Zsh-specific functions like
_arguments
,_describe
,_values
, and others.Would you like me to provide an example of how to structure a basic Zsh completion function? Or do you have any specific aspects of your Bash completion script that you'd like guidance on converting?