diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ce2c290 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# +# EditorConfig for consistent coding styles. +# Ref: https://editorconfig.org +# + +root = true + +[*] +indent_style = space +indent_size = 2 +tab_width = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..48c334c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# +# Exclude these files from release archives. +# + +.gitattributes export-ignore +.gitignore export-ignore + + +# +# Auto detect text files and perform LF normalization +# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ +# +* text=auto +eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8d7a87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# +# Ignore +# + +*.swp +.DS_Store diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..32a9180 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +© 2023 Nicolò Diamante + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc39d26 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +

Zchat Title

+ +Maximise your productivity and streamline your terminal experience with Zchat (zch) - a command-line productivity tool powered by OpenAI's [ChatGPT][chatgpt] models. Quickly generate Linux commands and code snippets from natural language queries without the need to manually search the web. Leverage AI capabilities to get accurate answers directly in your terminal in a time and effort-efficient manner. Furthermore, Zchat's Git integration makes working with version control even simpler - goodbye cheat sheets and notes. Get the job done quickly and easily. + +

+ +

IMG show ChatGPT conbined with the Terminal


+ +## Create your OpenAI API Key + +To use Zchat, you'll first need to obtain the API key. This can be done by generating a new secret key from your OpenAI account, which will be required for authentication. To get started, you can obtain the key by following these steps. First, log in to your [OpenAI account][open-ai-account]. Next, look for the "Create new secret key" option and click on it. + +

IMG show how to create new secret key

+ +Once you have obtained your [API Key][open-ai-API], integrating ChatGPT's services with Zchat is straightforward. Note that once you have copied the API Key and closed the pop-up window, you will no longer be able to access and view the key, making it essential that you store it in a secure and safe place. +

+ +

IMG show an example of a OpenAI API Key


+ +## Getting Started + +Download the repository via curl: + +```shell +sh -c "$(curl -fsSL https://raw.githubusercontent.com/nicolodiamante/zchat/HEAD/bootstrap.sh)" +``` + +Alternatively, clone manually: + +```shell +git clone https://github.com/nicolodiamante/zchat.git ~/zchat +``` + +Head over into the directory and then: + +```shell +cd utils && source install.sh +``` + +The script will search for the file zshrc, then append the file path `zchat/script` to the variable fpath and set the `OPENAI_API_KEY` variable. + +```shell +# Zchat path. +fpath=(~/zchat/script $fpath) +autoload -Uz zchat +``` +
+ +### Install via [Oh My Zsh][ohmyzsh] + +```shell +git clone https://github.com/nicolodiamante/zchat.git $ZSH_CUSTOM/plugins/zchat +``` + +- Add to your zshrc plugins array `plugins=(... zchat)` +- Paste your API Key at the `OPENAI_API_KEY` variable in your zshrc. +- Restart the shell to activate. +

+ +### Setting up dependencies + +Once the installation is complete, open your zshrc file. Paste your OpenAI API Key to the `OPENAI_API_KEY` variable. By default, the script uses the GPT-3.5-turbo model. For most basic tasks, there is not much difference between GPT-4 and GPT-3.5 models. To use the latest GPT-4 model, just change the current model to `gpt-4`. + +```shell +# Zchat dependencies. +export OPENAI_API_KEY="" +export OPENAI_GPT_MODEL="gpt-3.5-turbo" +``` + +> If you are not enrolled in the limited beta program for GPT-4, you will need to join a waiting list to use its API. Since developers have been given priority, it is unclear when non-developers will be granted access. Until that time, users should select the GPT-3.5-Turbo model as an alternative. + +
+ +## How to use Zchat + +```shell +zch +``` + +### Completion + +- Type `zch` to initiate the completion follow by the command that will be used as a basis for your query. +- Once you have written a natural language version of what you intend to do, press "enter" to execute it. +


+ +

Zchat Completion

+

+ +## Notes + +When you launch Zchat, it will automatically check if you are in a Git repository. If you are, it will provide relevant Git commands and guidance. If not, Zchat will help you access the right command line tools and find the best solution to complete your task. + +### Resources + +#### OpenAI + +- [OpenAI Documentation][intro] +- [OpenAI Models][open-ai-models] +- [OpenAI Chat][chat-completions] + +#### Zsh Documentations + +- [Documentation Index][zsh-docs] +- [User Guide][zsh-docs-guide] + +### Contribution + +Thank you for considering using Zchat. Any suggestions or feedback you may have for improvement are welcome. If you encounter any issues or bugs, please report them on the [issues page][issues].

+ +

+ +

Nicolò Diamante Portfolio

+ +

The MIT License

+ + +[open-ai-account]: https://chat.openai.com/auth/login +[open-ai-API]: https://beta.openai.com/account/api-keys +[chatgpt]: https://openai.com/blog/chatgpt +[open-ai-models]: https://platform.openai.com/docs/models +[intro]: https://platform.openai.com/docs/introduction +[chat-completions]: https://platform.openai.com/docs/guides/chat +[ohmyzsh]: https://github.com/robbyrussell/oh-my-zsh/ +[zsh-docs]: http://zsh.sourceforge.net/Doc +[zsh-docs-guide]: http://zsh.sourceforge.net/Guide/zshguide.html +[issues]: https://github.com/nicolodiamante/zchat/issues diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 0000000..46b977b --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# Determines the current user's shell. +[[ "$(basename -- "$SHELL")" == "zsh" ]] || exit 1 + +SOURCE=https://github.com/nicolodiamante/zchat +TARBALL="${SOURCE}/tarball/master" +TARGET="${HOME}/zchat" +TAR_CMD="tar -xzv -C "${TARGET}" --strip-components 1 --exclude .gitignore" + +INSTALL=./utils/install.sh + +is_executable() { + type "$1" > /dev/null 2>&1 +} + +# Checks which executable is available then downloads and installs. +if is_executable "git"; then + CMD="git clone ${SOURCE} ${TARGET}" +elif is_executable "curl"; then + CMD="curl -L ${TARBALL} | ${TAR_CMD}" +elif is_executable "wget"; then + CMD="wget --no-check-certificate -O - ${TARBALL} | ${TAR_CMD}" +fi + +if [[ -z "$CMD" ]]; then + echo 'No git, curl or wget available. Aborting!' +else + echo 'Installing zchat...' + mkdir -p "${TARGET}" && eval "${CMD}" && cd "${TARGET}" && source "${INSTALL}" +fi diff --git a/script/zch b/script/zch new file mode 100644 index 0000000..5b476fa --- /dev/null +++ b/script/zch @@ -0,0 +1,130 @@ +# +# Zchat - Streamline your terminal experience. +# By Nicolò Diamante +# https://github.com/nicolodiamante/zchat +# MIT License +# + +zch() { + if ! command -v jq >/dev/null; then + echo "zchat: jq is not found on your system. Please install jq and try again." + return 1 + fi + + if ! command -v curl &> /dev/null && ! command -v wget &> /dev/null; then + echo "" + echo "Error: curl or wget is not installed" + zle reset-prompt + return 1 + fi + + if [[ -z "$OPENAI_API_KEY" ]]; then + echo "zchat: the OPENAI_API_KEY appears to be missing. Please provide a valid API key and try again." + return 1 + fi + + if [[ -z "$OPENAI_GPT_MODEL" ]]; then + echo "zchat: the OPENAI_GPT_MODEL appears not to be set. By default this script is set to used GPT-3.5-turbo model. For many basic tasks, the difference between GPT-4 and GPT-3.5 models is not significant. However, in more complex reasoning situations, GPT-4 is much more capable than any of previous models. If you want to use the latest model version GPT-4 you just need to change the current number to 4. Keep in mind GPT-4 is currently in a limited beta and not everyone, even if they are on a paid plan, has access to GPT-4 API." + return 1 + else + chatGPT_model="$OPENAI_GPT_MODEL" + fi + + zchat_get_distribution_name() { + if [[ "$(uname)" -eq "Darwin" ]]; then + echo "$(sw_vers -productName) $(sw_vers -productVersion)" 2>/dev/null + else + echo "$(cat /etc/*-release 2>/dev/null | grep PRETTY_NAME | cut -d'"' -f2)" + fi + } + + zchat_get_os_prompt_injection() { + local OS=$(zchat_get_distribution_name) + if [[ -n "$OS" ]]; then + echo " for $OS" + else + echo "" + fi + } + +# Request body. + user_input="$@" + model="$chatGPT_model" + temperature=0.5 + top_p=0.0 + presence_penalty=0.0 + frequency_penalty=0.0 + max_tokens=257 + + # Check if current directory is a Git repository. + if [[ -d .git ]]; then + message="You are Zchat, my autocomplete script, and your goal is to help me navigate my Linux system, starting from the current directory, which is a git repository. What GIT command should I copy and paste into the terminal in order to achieve the desired result of ${user_input}? Provide only a valid GIT command${OS}, and nothing else. You do not write any human-readable explanations. If you fail to answer, return 'zchat: failed to generate command'." + else + message="You are Zchat, my autocomplete script, and your task is to assist me in navigating my Linux. All the questions I'm asking will be related to this subject. Now, I'd like you to answer this: ${user_input}. Please provide a valid command${OS} as a single line of text (no code blocks, quotes, or anything else outside the command itself) that takes into consideration my current directory (using either '.' or $(pwd)) and doesn't put my system at risk in any way. It's acceptable to chain commands, but prioritize one-liners if possible. If you can't find a suitable command, answer with 'zchat: failed to generate command'." + fi + + if command -v curl &> /dev/null; then + response=$(curl -s -X POST "https://api.openai.com/v1/chat/completions" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d "{ + \"model\": \"$model\", + \"messages\": [{\"role\": \"user\", \"content\": \"$message\"}], + \"temperature\": $temperature, + \"top_p\": $top_p, + \"max_tokens\": $max_tokens, + \"presence_penalty\": $presence_penalty, + \"frequency_penalty\": $frequency_penalty + }") + else + response=$(wget -qO- "https://api.openai.com/v1/chat/completions" \ + --header="Content-Type: application/json" \ + --header="Authorization: Bearer $OPENAI_API_KEY" \ + --post-data="{ + \"model\": \"$model\", + \"messages\": [{\"role\": \"user\", \"content\": \"$message\"}], + \"temperature\": $temperature, + \"top_p\": $top_p, + \"max_tokens\": $max_tokens, + \"presence_penalty\": $presence_penalty, + \"frequency_penalty\": $frequency_penalty + }") + fi + + # Add some colour. + autoload -Uz colors && colors + promp_default='%F{green}→%f' + promp_error='%F{red}→%f' + + ERROR_MESSAGE=$(printf "%s" "$response" | jq -r '(.error.message // "") | @json' | sed 's/^"//;s/"$//') + COMMAND=$(printf "%s" "$response" | jq -r '(.choices[0].message.content // "") | @json' | sed 's/^"//;s/"$//') + + # Output prompt. + if [[ -n "$ERROR_MESSAGE" ]]; then + echo "Error: $ERROR_MESSAGE" + elif [[ -n "$COMMAND" && -d .git ]]; then + if [[ "$COMMAND" =~ ^git ]]; then + print -P "$promp_default $COMMAND" + read -q "REPLY? Execute command? (y/n) " + if [[ "$REPLY" =~ ^[Yy]$ ]]; then + echo + eval "$COMMAND" + else + echo + print -P "$promp_error command not executed." + fi + else + print -P "$promp_error output does not begin with git and it will not be executed." + print -P "$COMMAND" + fi + elif [[ "$COMMAND" =~ "zchat: failed to generate command" || "$COMMAND" =~ "parse error:" ]]; then + print -P "$promp_error unable to generate a command." + print -P "$COMMAND" + elif [[ -n "$COMMAND" ]]; then + print -P "$promp_default $COMMAND" + eval "$COMMAND" + else + print -P "$promp_error unexpected response." + print -P "$COMMAND" + fi +} diff --git a/utils/install.sh b/utils/install.sh new file mode 100755 index 0000000..7399d24 --- /dev/null +++ b/utils/install.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +# +# Install zchat +# + +# Determines the current user's shell, if `zsh` then installs. +[[ "$(basename -- "$SHELL")" == "zsh" ]] || exit 1 + +# Check for Homebrew, else install. +echo 'Checking for Homebrew...' +if ! command -v brew >/dev/null; then + echo 'Brew is missing! Installing it...' + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +fi + +# Check for jq, else install. +if ! command -v jq >/dev/null; then + echo 'zchat: jp dependency is missing! Installing it...' + brew install jq +fi + +# Defines the PATHs. +SCRIPT="${HOME}/zchat/script" +ZSHRC="${ZDOTDIR:-${XDG_CONFIG_HOME/zsh:-$HOME}}/.zshrc" + +# Make script executable. +zchatat="${0:h}/script/zchat" +chmod +x $zchat + +if [[ -d "$SCRIPT" && -f "$ZSHRC" ]]; then + # Appends to `zshrc`. + cat << EOF >> ${ZSHRC} +# Zchat path. +fpath=(~/zchat/script \$fpath) +autoload -Uz zchat + +# Zchat dependencies. +export OPENAI_API_KEY="" +export OPENAI_GPT_MODEL="gpt-3.5-turbo" +EOF + echo 'zsh: appended zchat to zshrc.' + + # Reloads shell. + source "${ZSHRC}" +else + echo 'zsh: zshrc not found!' +fi diff --git a/zchat.plugin.zsh b/zchat.plugin.zsh new file mode 100644 index 0000000..cebd484 --- /dev/null +++ b/zchat.plugin.zsh @@ -0,0 +1,3 @@ +#!/bin/zsh + +fpath+="${0:h}/script"