Skip to content

Commit

Permalink
Non-interactive mode for --shell and integration changes
Browse files Browse the repository at this point in the history
  • Loading branch information
TheR1D committed Jan 28, 2024
1 parent 361d1ea commit b668b55
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 35 deletions.
12 changes: 2 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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

Expand Down
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -81,6 +94,11 @@ 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.
Expand Down Expand Up @@ -392,6 +410,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] │
Expand Down
34 changes: 21 additions & 13 deletions sgpt/app.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -24,7 +25,7 @@

def main(
prompt: str = typer.Argument(
None,
"",
show_default=False,
help="The prompt to generate completions for.",
),
Expand All @@ -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",
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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),
Expand Down
3 changes: 1 addition & 2 deletions sgpt/handlers/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ def _handle_with_markdown(self, prompt: str, **kwargs: Any) -> str:
with Live(
Markdown(markup="", code_theme=self.theme_name),
console=Console(),
refresh_per_second=8,
) as live:
if self.disable_stream:
live.update(
Expand All @@ -45,7 +44,7 @@ def _handle_with_markdown(self, prompt: str, **kwargs: Any) -> str:
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
Expand Down
27 changes: 27 additions & 0 deletions sgpt/integration.py
Original file line number Diff line number Diff line change
@@ -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
"""
23 changes: 16 additions & 7 deletions sgpt/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
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:
Expand Down Expand Up @@ -65,17 +66,25 @@ def wrapper(cls: Any, value: str) -> None:
@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
Expand Down
18 changes: 18 additions & 0 deletions tests/test_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit b668b55

Please sign in to comment.