Merge pull request #10 from Myzel394/add-oh-my-zsh-support

This commit is contained in:
Myzel394 2025-06-07 23:47:56 +02:00 committed by GitHub
commit bcea2d2033
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 76 additions and 26 deletions

View File

@ -1,5 +1,6 @@
# zsh-copilot **NOTICE**: I'm slowly migrating my repositories to my own Git server. Please visit this repository at [https://git.myzel394.app/Myzel394/zsh-copilot](https://git.myzel394.app/Myzel394/zsh-copilot) for the latest updates.
# zsh-copilot
Get suggestions **truly** in your shell. No `suggest` bullshit. Just press `CTRL + Z` and get your suggestion. Get suggestions **truly** in your shell. No `suggest` bullshit. Just press `CTRL + Z` and get your suggestion.
@ -15,12 +16,31 @@ Please make sure you have the following dependencies installed:
* [jq](https://github.com/jqlang/jq) * [jq](https://github.com/jqlang/jq)
* [curl](https://github.com/curl/curl) * [curl](https://github.com/curl/curl)
### Oh My Zsh
1. Clone `zsh-copilot` into `$ZSH_CUSTOM/plugins` (by default ~/.config/oh-my-zsh/custom/plugins)
```sh ```sh
git clone https://github.com/Myzel394/zsh-copilot.git ~/.zsh-copilot git clone https://git.myzel394.app/Myzel394/zsh-copilot ${ZSH_CUSTOM:-~/.config/oh-my-zsh/custom}/plugins/zsh-copilot
echo "source ~/.zsh-copilot/zsh-copilot.plugin.zsh" >> ~/.zshrc
``` ```
### Configuration 2. Add `zsh-copilot` to the plugins array in your `.zshrc` file:
```bash
plugins=(
# your other plugins...
zsh-autosuggestions
)
```
### Manual Installation
```sh
git clone https://git.myzel394.app/Myzel394/zsh-copilot ~/.config/zsh-copilot
echo "source ~/.config/zsh-copilot/zsh-copilot.plugin.zsh" >> ~/.zshrc
```
## Configuration
You need to have an API key for either OpenAI or Anthropic to use this plugin. Expose this via the appropriate environment variable: You need to have an API key for either OpenAI or Anthropic to use this plugin. Expose this via the appropriate environment variable:

View File

@ -1,3 +1,5 @@
#!/usr/bin/env zsh
# Default key binding # Default key binding
(( ! ${+ZSH_COPILOT_KEY} )) && (( ! ${+ZSH_COPILOT_KEY} )) &&
typeset -g ZSH_COPILOT_KEY='^z' typeset -g ZSH_COPILOT_KEY='^z'
@ -10,63 +12,82 @@
typeset -g ZSH_COPILOT_DEBUG=false typeset -g ZSH_COPILOT_DEBUG=false
# New option to select AI provider # New option to select AI provider
(( ! ${+ZSH_COPILOT_AI_PROVIDER} )) && if [[ -z "$ZSH_COPILOT_AI_PROVIDER" ]]; then
if [[ -n "$OPENAI_API_KEY" ]]; then
typeset -g ZSH_COPILOT_AI_PROVIDER="openai" typeset -g ZSH_COPILOT_AI_PROVIDER="openai"
elif [[ -n "$ANTHROPIC_API_KEY" ]]; then
typeset -g ZSH_COPILOT_AI_PROVIDER="anthropic"
else
echo "No AI provider selected. Please set either OPENAI_API_KEY or ANTHROPIC_API_KEY."
return 1
fi
fi
# System prompt # System prompt
read -r -d '' SYSTEM_PROMPT <<- EOM if [[ -z "$ZSH_COPILOT_SYSTEM_PROMPT" ]]; then
read -r -d '' ZSH_COPILOT_SYSTEM_PROMPT <<- EOM
You will be given the raw input of a shell command. You will be given the raw input of a shell command.
Your task is to either complete the command or provide a new command that you think the user is trying to type. Your task is to either complete the command or provide a new command that you think the user is trying to type.
If you return a completely new command for the user, prefix is with an equal sign (=). If you return a completely new command for the user, prefix is with an equal sign (=).
If you return a completion for the user's command, prefix it with a plus sign (+). If you return a completion for the user's command, prefix it with a plus sign (+).
MAKE SURE TO ONLY INCLUDE THE REST OF THE COMPLETION!!! MAKE SURE TO ONLY INCLUDE THE REST OF THE COMPLETION!!!
Do not write any leading or trailing characters except if required for the completion to work. Do not write any leading or trailing characters except if required for the completion to work.
Only respond with either a completion or a new command, not both. Only respond with either a completion or a new command, not both.
Your response may only start with either a plus sign or an equal sign. Your response may only start with either a plus sign or an equal sign.
Your response MAY NOT start with both! This means that your response IS NOT ALLOWED to start with '+=' or '=+'. Your response MAY NOT start with both! This means that your response IS NOT ALLOWED to start with '+=' or '=+'.
You MAY explain the command by writing a short line after the comment symbol (#).
Your response MAY NOT contain any newlines!
Do NOT add any additional text, comments, or explanations to your response.
Do not ask for more information, you won't receive it. Do not ask for more information, you won't receive it.
Your response will be run in the user's shell. Your response will be run in the user's shell.
Make sure input is escaped correctly if needed so. Make sure input is escaped correctly if needed so.
Your input should be able to run without any modifications to it. Your input should be able to run without any modifications to it.
Don't you dare to return anything else other than a shell command!!!
DO NOT INTERACT WITH THE USER IN NATURAL LANGUAGE! If you do, you will be banned from the system. DO NOT INTERACT WITH THE USER IN NATURAL LANGUAGE! If you do, you will be banned from the system.
Note that the double quote sign is escaped. Keep this in mind when you create quotes. Note that the double quote sign is escaped. Keep this in mind when you create quotes.
Here are two examples: Here are two examples:
* User input: 'list files in current directory'; Your response: '=ls' (ls is the builtin command for listing files) * User input: 'list files in current directory'; Your response: '=ls' (ls is the builtin command for listing files)
* User input: 'cd /tm'; Your response: '+p' (/tmp is the standard temp folder on linux and mac). * User input: 'cd /tm'; Your response: '+p' (/tmp is the standard temp folder on linux and mac).
EOM EOM
if [[ "$OSTYPE" == "darwin"* ]]; then
SYSTEM="Your system is ${$(sw_vers | xargs | sed 's/ /./g')}."
else
SYSTEM="Your system is ${$(cat /etc/*-release | xargs | sed 's/ /,/g')}."
fi fi
function _suggest_ai() { function _suggest_ai() {
local OPENAI_API_URL=${OPENAI_API_URL:-"api.openai.com"} #### Prepare environment
local ANTHROPIC_API_URL=${ANTHROPIC_API_URL:-"api.anthropic.com"} local openai_api_url=${OPENAI_API_URL:-"api.openai.com"}
local anthropic_api_url=${ANTHROPIC_API_URL:-"api.anthropic.com"}
local context_info="" local context_info=""
if [[ "$ZSH_COPILOT_SEND_CONTEXT" == 'true' ]]; then if [[ "$ZSH_COPILOT_SEND_CONTEXT" == 'true' ]]; then
context_info="Context: You are user $(whoami) with id $(id) in directory $(pwd). local system
Your shell is $(echo $SHELL) and your terminal is $(echo $TERM) running on $(uname -a).
$SYSTEM" if [[ "$OSTYPE" == "darwin"* ]]; then
system="Your system is ${$(sw_vers | xargs | sed 's/ /./g')}."
else
system="Your system is ${$(cat /etc/*-release | xargs | sed 's/ /,/g')}."
fi fi
# Get input context_info="Context: You are user $(whoami) with id $(id) in directory $(pwd).
Your shell is $(echo $SHELL) and your terminal is $(echo $TERM) running on $(uname -a).
$system"
fi
##### Get input
local input=$(echo "${BUFFER:0:$CURSOR}" | tr '\n' ';') local input=$(echo "${BUFFER:0:$CURSOR}" | tr '\n' ';')
input=$(echo "$input" | sed 's/"/\\"/g') input=$(echo "$input" | sed 's/"/\\"/g')
_zsh_autosuggest_clear _zsh_autosuggest_clear
zle -R "Thinking..." zle -R "Thinking..."
local full_prompt=$(echo "$SYSTEM_PROMPT $context_info" | tr -d '\n') local full_prompt=$(echo "$ZSH_COPILOT_SYSTEM_PROMPT $context_info" | tr -d '\n')
##### Fetch message
local data local data
local response local response
local message
if [[ "$ZSH_COPILOT_AI_PROVIDER" == "openai" ]]; then if [[ "$ZSH_COPILOT_AI_PROVIDER" == "openai" ]]; then
# OpenAI's API payload
data="{ data="{
\"model\": \"gpt-4o-mini\", \"model\": \"gpt-4o-mini\",
\"messages\": [ \"messages\": [
@ -80,15 +101,17 @@ function _suggest_ai() {
} }
] ]
}" }"
response=$(curl "https://${OPENAI_API_URL}/v1/chat/completions" \ response=$(curl "https://${openai_api_url}/v1/chat/completions" \
--silent \ --silent \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Authorization: Bearer $OPENAI_API_KEY" \
-d "$data") -d "$data")
local message=$(echo "$response" | jq -r '.choices[0].message.content')
message=$(echo "$response" | tr -d '\n' | jq -r '.choices[0].message.content')
elif [[ "$ZSH_COPILOT_AI_PROVIDER" == "anthropic" ]]; then elif [[ "$ZSH_COPILOT_AI_PROVIDER" == "anthropic" ]]; then
# Anthropic's API payload
data="{ data="{
\"model\": \"claude-3-5-sonnet-20240620\", \"model\": \"claude-3-5-sonnet-latest\",
\"max_tokens\": 1000, \"max_tokens\": 1000,
\"system\": \"$full_prompt\", \"system\": \"$full_prompt\",
\"messages\": [ \"messages\": [
@ -98,18 +121,21 @@ function _suggest_ai() {
} }
] ]
}" }"
response=$(curl "https://${ANTHROPIC_API_URL}/v1/messages" \ response=$(curl "https://${anthropic_api_url}/v1/messages" \
--silent \ --silent \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \ -H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \ -H "anthropic-version: 2023-06-01" \
-d "$data") -d "$data")
local message=$(echo "$response" | jq -r '.content[0].text')
message=$(echo "$response" | tr -d '\n' | jq -r '.content[0].text')
else else
echo "Invalid AI provider selected. Please choose 'openai' or 'anthropic'." echo "Invalid AI provider selected. Please choose 'openai' or 'anthropic'."
return 1 return 1
fi fi
##### Process response
local first_char=${message:0:1} local first_char=${message:0:1}
local suggestion=${message:1:${#message}} local suggestion=${message:1:${#message}}
@ -118,6 +144,8 @@ function _suggest_ai() {
echo "$(date);INPUT:$input;RESPONSE:$response;FIRST_CHAR:$first_char;SUGGESTION:$suggestion:DATA:$data" >> /tmp/zsh-copilot.log echo "$(date);INPUT:$input;RESPONSE:$response;FIRST_CHAR:$first_char;SUGGESTION:$suggestion:DATA:$data" >> /tmp/zsh-copilot.log
fi fi
##### And now, let's actually show the suggestion to the user!
if [[ "$first_char" == '=' ]]; then if [[ "$first_char" == '=' ]]; then
# Reset user input # Reset user input
BUFFER="" BUFFER=""
@ -135,8 +163,10 @@ function zsh-copilot() {
echo "Configurations:" echo "Configurations:"
echo " - ZSH_COPILOT_KEY: Key to press to get suggestions (default: ^z, value: $ZSH_COPILOT_KEY)." echo " - ZSH_COPILOT_KEY: Key to press to get suggestions (default: ^z, value: $ZSH_COPILOT_KEY)."
echo " - ZSH_COPILOT_SEND_CONTEXT: If \`true\`, zsh-copilot will send context information (whoami, shell, pwd, etc.) to the AI model (default: true, value: $ZSH_COPILOT_SEND_CONTEXT)." echo " - ZSH_COPILOT_SEND_CONTEXT: If \`true\`, zsh-copilot will send context information (whoami, shell, pwd, etc.) to the AI model (default: true, value: $ZSH_COPILOT_SEND_CONTEXT)."
echo " - ZSH_COPILOT_AI_PROVIDER: AI provider to use ('openai' or 'anthropic', default: openai, value: $ZSH_COPILOT_AI_PROVIDER)." echo " - ZSH_COPILOT_AI_PROVIDER: AI provider to use ('openai' or 'anthropic', value: $ZSH_COPILOT_AI_PROVIDER)."
echo " - ZSH_COPILOT_SYSTEM_PROMPT: System prompt to use for the AI model (uses a built-in prompt by default)."
} }
zle -N _suggest_ai zle -N _suggest_ai
bindkey $ZSH_COPILOT_KEY _suggest_ai bindkey "$ZSH_COPILOT_KEY" _suggest_ai