Non-interactive mode for --shell and integration changes (#455)

* Added --interaction that works with --shell option.
* Changed shell integrations to use new --no-interaction option.
* Moved shell integrations into dedicated file integration.py.
* Changed --install-integration logic to install integrations without downloading sh scripts.
* Removed validation for PROMPT argument, empty string by default.
* Fixing an issue when sgpt is being called from non-interactive shell environments.
* Fixed and optimised Dockerfile.
* README.md improvements, and new feature examples.
* New funny demo video.
This commit is contained in:
Farkhod Sadykov
2024-01-28 05:03:44 +01:00
committed by GitHub
parent 361d1eabf8
commit c48926a4a4
8 changed files with 125 additions and 38 deletions
+2 -10
View File
@@ -1,18 +1,10 @@
FROM python:3-slim
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_ROOT_USER_ACTION ignore
WORKDIR /app
COPY . /app
RUN pip install --no-cache --upgrade pip \
&& pip install --no-cache /app \
&& addgroup --system app && adduser --system --group --home /home/app app \
&& mkdir -p /tmp/shell_gpt \
&& chown -R app:app /tmp/shell_gpt
USER app
RUN apt-get update && apt-get install -y gcc
RUN pip install --no-cache /app && mkdir -p /tmp/shell_gpt
VOLUME /tmp/shell_gpt
+39 -5
View File
@@ -1,7 +1,7 @@
# ShellGPT
A command-line productivity tool powered by AI large language models (LLM). This command-line tool offers streamlined generation of **shell commands, code snippets, documentation**, eliminating the need for external resources (like Google search). Supports Linux, macOS, Windows and compatible with all major Shells like PowerShell, CMD, Bash, Zsh, etc.
https://github.com/TheR1D/shell_gpt/assets/16740832/721ddb19-97e7-428f-a0ee-107d027ddd59
https://github.com/TheR1D/shell_gpt/assets/16740832/9197283c-db6a-4b46-bfea-3eb776dd9093
## Installation
```shell
@@ -35,6 +35,19 @@ Error Detected: Memory allocation failed at line 12.
Possible Solution: Consider increasing memory allocation or optimizing application memory usage.
```
You can also use all kind of redirection operators to pass input:
```shell
sgpt "summarise" < document.txt
# -> The document discusses the impact...
sgpt << EOF
What is the best way to lear Golang.
Provide simple hello world example.
EOF
# -> The best way to learn Golang...
sgpt <<< "What is the best way to learn shell redirects?"
# -> The best way to learn shell redirects is through...
```
### Shell commands
Have you ever found yourself forgetting common shell commands, such as `find`, and needing to look up the syntax online? With `--shell` or shortcut `-s` option, you can quickly generate and execute the commands you need right in the terminal.
@@ -65,14 +78,14 @@ sgpt -s "start nginx container, mount ./index.html"
# -> [E]xecute, [D]escribe, [A]bort: e
```
We can still use pipes to pass input to `sgpt` and get generate shell commands:
We can still use pipes to pass input to `sgpt` and generate shell commands:
```shell
cat data.json | sgpt -s "POST localhost with json"
sgpt -s "POST localhost with" < data.json
# -> curl -X POST -H "Content-Type: application/json" -d '{"a": 1, "b": 2}' http://localhost
# -> [E]xecute, [D]escribe, [A]bort: e
```
Applying additional shell magic in our prompt, in this example passing file names to ffmpeg:
Applying additional shell magic in our prompt, in this example passing file names to `ffmpeg`:
```shell
ls
# -> 1.mp4 2.mp4 3.mp4
@@ -81,9 +94,14 @@ sgpt -s "ffmpeg combine $(ls -m) into one video file without audio."
# -> [E]xecute, [D]escribe, [A]bort: e
```
If you would like to pass generated shell command using pipe, you can use `--no-interaction` option. This will disable interactive mode and will print generated command to stdout. In this example we are using `pbcopy` to copy generated command to clipboard:
```shell
sgpt -s "find all json files in current folder" --no-interaction | pbcopy
```
### Shell integration
Shell integration enables the use of ShellGPT with hotkeys in your terminal, supported by both Bash and ZSH shells. This feature puts `sgpt` completions directly into terminal buffer (input line), allowing for immediate editing of suggested commands.
This is a **very handy feature**, which allows you to use `sgpt` shell completions directly in your terminal, without the need to type `sgpt` with prompt and arguments. Shell integration enables the use of ShellGPT with hotkeys in your terminal, supported by both Bash and ZSH shells. This feature puts `sgpt` completions directly into terminal buffer (input line), allowing for immediate editing of suggested commands.
https://github.com/TheR1D/shell_gpt/assets/16740832/bead0dab-0dd9-436d-88b7-6abfb2c556c1
@@ -248,6 +266,21 @@ Entering REPL mode, press Ctrl+C to exit.
It is a Python script that uses the random module to generate and print a random integer.
```
You can also enter REPL mode with initial prompt by passing it as an argument or stdin or even both:
```shell
sgpt --repl temp < my_app.py
```
```text
Entering REPL mode, press Ctrl+C to exit.
──────────────────────────────────── Input ────────────────────────────────────
name = input("What is your name?")
print(f"Hello {name}")
───────────────────────────────────────────────────────────────────────────────
>>> What is this code about?
The snippet of code you've provided is written in Python. It prompts the user...
>>> Follow up questions...
```
### Function calling
[Function calls](https://platform.openai.com/docs/guides/function-calling) is a powerful feature OpenAI provides. It allows LLM to execute functions in your system, which can be used to accomplish a variety of tasks. To install [default functions](https://github.com/TheR1D/shell_gpt/tree/main/sgpt/default_functions/) run:
```shell
@@ -392,6 +425,7 @@ Possible options for `CODE_THEME`: https://pygments.org/styles/
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Assistance Options ─────────────────────────────────────────────────────────────────────────────────────╮
│ --shell -s Generate and execute shell commands. │
│ --interaction --no-interaction Interactive mode for --shell option. [default: interaction] │
│ --describe-shell -d Describe a shell command. │
│ --code -c Generate only code. │
│ --functions --no-functions Allow function calls. [default: functions] │
+1 -1
View File
@@ -1 +1 @@
__version__ = "1.1.0"
__version__ = "1.2.0"
+21 -13
View File
@@ -1,10 +1,11 @@
# To allow users to use arrow keys in the REPL.
import os
# To allow users to use arrow keys in the REPL.
import readline # noqa: F401
import sys
import typer
from click import BadArgumentUsage, MissingParameter
from click import BadArgumentUsage
from click.types import Choice
from sgpt.config import cfg
@@ -24,7 +25,7 @@ from sgpt.utils import (
def main(
prompt: str = typer.Argument(
None,
"",
show_default=False,
help="The prompt to generate completions for.",
),
@@ -51,6 +52,11 @@ def main(
help="Generate and execute shell commands.",
rich_help_panel="Assistance Options",
),
interaction: bool = typer.Option(
True,
help="Interactive mode for --shell option.",
rich_help_panel="Assistance Options",
),
describe_shell: bool = typer.Option(
False,
"--describe-shell",
@@ -156,20 +162,22 @@ def main(
# but rest of the stdin to be used as a inputs. For example:
# echo "hello\n__sgpt__eof__\nThis is input" | sgpt --repl temp
# In this case, "hello" will be used as a init prompt, and
# "This is input" will be used as a input to the REPL.
# "This is input" will be used as "interactive" input to the REPL.
# This is useful to test REPL with some initial context.
for line in sys.stdin:
if "__sgpt__eof__" in line:
break
stdin += line
prompt = f"{stdin}\n\n{prompt}" if prompt else stdin
# Switch to stdin for interactive input.
if os.name == "posix":
sys.stdin = open("/dev/tty", "r")
elif os.name == "nt":
sys.stdin = open("CON", "r")
if not prompt and not editor and not repl:
raise MissingParameter(param_hint="PROMPT", param_type="string")
try:
# Switch to stdin for interactive input.
if os.name == "posix":
sys.stdin = open("/dev/tty", "r")
elif os.name == "nt":
sys.stdin = open("CON", "r")
except OSError:
# Non-interactive shell.
pass
if sum((shell, describe_shell, code)) > 1:
raise BadArgumentUsage(
@@ -225,7 +233,7 @@ def main(
functions=function_schemas,
)
while shell:
while shell and interaction:
option = typer.prompt(
text="[E]xecute, [D]escribe, [A]bort",
type=Choice(("e", "d", "a", "y"), case_sensitive=False),
+1 -2
View File
@@ -35,7 +35,6 @@ class Handler:
with Live(
Markdown(markup="", code_theme=self.theme_name),
console=Console(),
refresh_per_second=8,
) as live:
if self.disable_stream:
live.update(
@@ -45,7 +44,7 @@ class Handler:
for word in self.get_completion(messages=messages, **kwargs):
full_completion += word
live.update(
Markdown(full_completion, code_theme=self.theme_name),
Markdown(markup=full_completion, code_theme=self.theme_name),
refresh=not self.disable_stream,
)
return full_completion
+27
View File
@@ -0,0 +1,27 @@
bash_integration = """
# Shell-GPT integration BASH v0.2
_sgpt_bash() {
if [[ -n "$READLINE_LINE" ]]; then
READLINE_LINE=$(sgpt --shell <<< "$READLINE_LINE" --no-interaction)
READLINE_POINT=${#READLINE_LINE}
fi
}
bind -x '"\\C-l": _sgpt_bash'
# Shell-GPT integration BASH v0.2
"""
zsh_integration = """
# Shell-GPT integration ZSH v0.2
_sgpt_zsh() {
if [[ -n "$BUFFER" ]]; then
_sgpt_prev_cmd=$BUFFER
BUFFER+="⌛"
zle -I && zle redisplay
BUFFER=$(sgpt --shell <<< "$_sgpt_prev_cmd" --no-interaction)
zle end-of-line
fi
}
zle -N _sgpt_zsh
bindkey ^l _sgpt_zsh
# Shell-GPT integration ZSH v0.2
"""
+16 -7
View File
@@ -5,9 +5,10 @@ from tempfile import NamedTemporaryFile
from typing import Any, Callable
import typer
from click import BadParameter
from click import BadParameter, UsageError
from sgpt.__version__ import __version__
from sgpt.integration import bash_integration, zsh_integration
def get_edited_prompt() -> str:
@@ -65,17 +66,25 @@ def option_callback(func: Callable) -> Callable: # type: ignore
@option_callback
def install_shell_integration(*_args: Any) -> None:
"""
Installs shell integration. Currently only supports Linux.
Installs shell integration. Currently only supports ZSH and Bash.
Allows user to get shell completions in terminal by using hotkey.
Allows user to edit shell command right away in terminal.
Replaces current "buffer" of the shell with the completion.
"""
# TODO: Add support for Windows.
# TODO: Implement updates.
if platform.system() == "Windows":
typer.echo("Windows is not supported yet.")
shell = os.getenv("SHELL", "")
if shell == "/bin/zsh":
typer.echo("Installing ZSH integration...")
with open(os.path.expanduser("~/.zshrc"), "a", encoding="utf-8") as file:
file.write(zsh_integration)
elif shell == "/bin/bash":
typer.echo("Installing Bash integration...")
with open(os.path.expanduser("~/.bashrc"), "a", encoding="utf-8") as file:
file.write(bash_integration)
else:
url = "https://raw.githubusercontent.com/TheR1D/shell_gpt/shell-integrations/install.sh"
os.system(f'sh -c "$(curl -fsSL {url})"')
raise UsageError("ShellGPT integrations only available for ZSH and Bash.")
typer.echo("Done! Restart your shell to apply changes.")
@option_callback
+18
View File
@@ -159,3 +159,21 @@ def test_shell_and_describe_shell(completion):
completion.assert_not_called()
assert result.exit_code == 2
assert "Error" in result.stdout
@patch("openai.resources.chat.Completions.create")
def test_shell_no_interaction(completion):
completion.return_value = comp_chunks("git commit -m test")
role = SystemRole.get(DefaultRoles.SHELL.value)
args = {
"prompt": "make a commit using git",
"--shell": True,
"--no-interaction": True,
}
result = runner.invoke(app, cmd_args(**args))
completion.assert_called_once_with(**comp_args(role, args["prompt"]))
assert result.exit_code == 0
assert "git commit" in result.stdout
assert "[E]xecute" not in result.stdout