From d6e102e63eae01a99ec99797852e9a02f3c02cf1 Mon Sep 17 00:00:00 2001 From: lpgn Date: Fri, 7 Feb 2025 23:19:58 +0000 Subject: [PATCH 1/5] feat: Add Google Gemini provider This commit adds a new provider for Google Gemini, allowing users to select Gemini as their LLM provider in ShellOracle. The following changes were made: - Created `src/shelloracle/providers/google.py` with the `Google` provider class. - Added `google-generativeai` as a dependency in `pyproject.toml`. - Modified `src/shelloracle/providers/__init__.py` to include the new provider. - Updated `~/.shelloracle/config.toml` to include a configuration section for the Google provider. --- pyproject.toml | 3 ++- src/shelloracle/providers/__init__.py | 12 +++++++-- src/shelloracle/providers/google.py | 37 +++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 src/shelloracle/providers/google.py diff --git a/pyproject.toml b/pyproject.toml index dcae99d..a19c43a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,8 @@ dependencies = [ "prompt-toolkit", "yaspin", "tomlkit", - "tomli >= 1.1.0; python_version < '3.11'" + "tomli >= 1.1.0; python_version < '3.11'", + "google-generativeai" ] [project.scripts] diff --git a/src/shelloracle/providers/__init__.py b/src/shelloracle/providers/__init__.py index 7734d4e..eceb6c0 100644 --- a/src/shelloracle/providers/__init__.py +++ b/src/shelloracle/providers/__init__.py @@ -79,8 +79,16 @@ def _providers() -> dict[str, type[Provider]]: from shelloracle.providers.ollama import Ollama from shelloracle.providers.openai import OpenAI from shelloracle.providers.xai import XAI - - return {Ollama.name: Ollama, OpenAI.name: OpenAI, LocalAI.name: LocalAI, XAI.name: XAI, Deepseek.name: Deepseek} + from shelloracle.providers.google import Google + + return { + Ollama.name: Ollama, + OpenAI.name: OpenAI, + LocalAI.name: LocalAI, + XAI.name: XAI, + Deepseek.name: Deepseek, + Google.name: Google, + } def get_provider(name: str) -> type[Provider]: diff --git a/src/shelloracle/providers/google.py b/src/shelloracle/providers/google.py new file mode 100644 index 0000000..af31a7b --- /dev/null +++ b/src/shelloracle/providers/google.py @@ -0,0 +1,37 @@ +from collections.abc import AsyncIterator + +import google.generativeai as genai + +from shelloracle.providers import Provider, ProviderError, Setting, system_prompt + + +class Google(Provider): + name = "Google" + + api_key = Setting(default="") + model = Setting(default="gemini-pro") # Assuming a default model name + + def __init__(self): + if not self.api_key: + msg = "No API key provided" + raise ProviderError(msg) + genai.configure(api_key=self.api_key) + self.model_instance = genai.GenerativeModel(self.model) + + + async def generate(self, prompt: str) -> AsyncIterator[str]: + try: + response = await self.model_instance.generate_content_async( + [ + {"role": "user", "parts": [system_prompt]}, + {"role": "model", "parts": ["Okay."]}, # Gemini requires a model response before user input + {"role": "user", "parts": [prompt]}, + ], + stream=True + ) + + async for chunk in response: + yield chunk.text + except Exception as e: + msg = f"Something went wrong while querying Google Gemini: {e}" + raise ProviderError(msg) from e \ No newline at end of file From af8190b96f934f1b61481e0b9ee8c9b2002e6086 Mon Sep 17 00:00:00 2001 From: lpgn Date: Sat, 8 Feb 2025 16:39:21 +0000 Subject: [PATCH 2/5] feat: Add fish shell support This commit introduces support for the fish shell. Changes include: - Updated README to reflect fish shell support. - Modified bootstrap script to handle fish shell configuration. - Updated system prompt to be shell-agnostic. - Added fish shell script for keybindings. Note: There is a known bug where the LLM output is partially doubled, but the functionality works correctly. --- README.md | 4 ++-- src/shelloracle/bootstrap.py | 30 ++++++++++++++------------ src/shelloracle/providers/__init__.py | 4 ++-- src/shelloracle/shell/shelloracle.fish | 9 ++++++++ 4 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 src/shelloracle/shell/shelloracle.fish diff --git a/README.md b/README.md index 045eb87..3b8ecfd 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Upgrading to the latest version of ShellOracle is just as simple! ## Usage -ShellOracle is designed to be used as a BASH/ZSH widget activated by the CTRL+F keyboard shortcut. +ShellOracle is designed to be used as a BASH/ZSH/fish widget activated by the CTRL+F keyboard shortcut. 1. Press CTRL+F 2. Describe your command @@ -122,7 +122,7 @@ behavior. ### Software -ShellOracle supports BASH and ZSH on macOS and Linux. +ShellOracle supports BASH, ZSH and fish on macOS and Linux. ### Hardware diff --git a/src/shelloracle/bootstrap.py b/src/shelloracle/bootstrap.py index f00a430..f475086 100644 --- a/src/shelloracle/bootstrap.py +++ b/src/shelloracle/bootstrap.py @@ -3,6 +3,7 @@ import inspect import shutil from pathlib import Path +import os from typing import TYPE_CHECKING, Any import tomlkit @@ -38,7 +39,7 @@ def replace_home_with_tilde(path: Path) -> Path: return Path("~") / relative_path -supported_shells = ("zsh", "bash") +supported_shells = ("zsh", "bash", "fish") def get_installed_shells() -> list[str]: @@ -49,18 +50,24 @@ def get_bundled_script_path(shell: str) -> Path: shell_dir = Path(__file__).parent / "shell" if shell == "zsh": return shell_dir / "shelloracle.zsh" + if shell == "fish": + return shell_dir / "shelloracle.fish" return shell_dir / "shelloracle.bash" def get_script_path(shell: str) -> Path: if shell == "zsh": return Path.home() / ".shelloracle.zsh" + if shell == "fish": + return Path.home() / ".shelloracle.fish" return Path.home() / ".shelloracle.bash" def get_rc_path(shell: str) -> Path: if shell == "zsh": return Path.home() / ".zshrc" + if shell == "fish": + return Path.home() / ".config/fish/config.fish" return Path.home() / ".bashrc" @@ -75,10 +82,13 @@ def update_rc(shell: str) -> None: rc_path = get_rc_path(shell) rc_path.touch(exist_ok=True) with rc_path.open("r") as file: - zshrc = file.read() - shelloracle_script = get_script_path(shell) - line = f"[ -f {shelloracle_script} ] && source {shelloracle_script}" - if line not in zshrc: + rc_content = file.read() + if shell == "fish": + line = f"source {get_script_path(shell)}" + else: + shelloracle_script = get_script_path(shell) + line = f"[ -f {shelloracle_script} ] && source {shelloracle_script}" + if line not in rc_content: with rc_path.open("a") as file: file.write("\n") file.write(line) @@ -116,15 +126,7 @@ def write_shelloracle_config(provider: type[Provider], settings: dict[str, Any]) def install_keybindings() -> None: - if not (shells := get_installed_shells()): - print_warning( - "Cannot install keybindings: no compatible shells found. " - f"Supported shells: {', '.join(supported_shells)}" - ) - return - if confirm("Enable terminal keybindings and update rc?", suffix=" ([y]/n) ") is False: - return - for shell in shells: + for shell in get_installed_shells(): write_script_home(shell) update_rc(shell) diff --git a/src/shelloracle/providers/__init__.py b/src/shelloracle/providers/__init__.py index eceb6c0..94dc0af 100644 --- a/src/shelloracle/providers/__init__.py +++ b/src/shelloracle/providers/__init__.py @@ -9,9 +9,9 @@ from collections.abc import AsyncIterator system_prompt = ( - "Based on the following user description, generate a corresponding Bash command. Focus solely " + "Based on the following user description, generate a corresponding shell command. Focus solely " "on interpreting the requirements and translating them into a single, executable Bash command. " - "Ensure accuracy and relevance to the user's description. The output should be a valid Bash " + "Ensure accuracy and relevance to the user's description. The output should be a valid shell " "command that directly aligns with the user's intent, ready for execution in a command-line " "environment. Do not output anything except for the command. No code block, no English explanation, " "no newlines, and no start/end tags." diff --git a/src/shelloracle/shell/shelloracle.fish b/src/shelloracle/shell/shelloracle.fish new file mode 100644 index 0000000..2637fb6 --- /dev/null +++ b/src/shelloracle/shell/shelloracle.fish @@ -0,0 +1,9 @@ +function __shelloracle__ + set -l output (shor) + if test $status -ne 0 + return $status + end + commandline -r -- $output +end + +bind \cf __shelloracle__ \ No newline at end of file From e41788d68f680f820b0c70d48dd2d7ee0f3667f1 Mon Sep 17 00:00:00 2001 From: lpgn Date: Sat, 8 Feb 2025 16:58:58 +0000 Subject: [PATCH 3/5] Fix: Reintroduce confirmation prompt --- src/shelloracle/bootstrap.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/shelloracle/bootstrap.py b/src/shelloracle/bootstrap.py index f475086..32a8d6e 100644 --- a/src/shelloracle/bootstrap.py +++ b/src/shelloracle/bootstrap.py @@ -126,11 +126,17 @@ def write_shelloracle_config(provider: type[Provider], settings: dict[str, Any]) def install_keybindings() -> None: + if not (shells := get_installed_shells()): + print_warning( + "Cannot install keybindings: no compatible shells found. " + f"Supported shells: {' '.join(supported_shells)}" + ) + return + if confirm("Enable terminal keybindings and update rc?", suffix=" ([y]/n) ") is False: + return for shell in get_installed_shells(): write_script_home(shell) update_rc(shell) - - def user_configure_settings(provider: type[Provider]) -> dict[str, Any]: settings = {} for name, setting in get_settings(provider): From 6542719b06dfbe736696677d1616195103047c38 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 8 Feb 2025 20:52:18 -0500 Subject: [PATCH 4/5] Update __init__.py --- src/shelloracle/providers/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shelloracle/providers/__init__.py b/src/shelloracle/providers/__init__.py index 5f73097..7c3467a 100644 --- a/src/shelloracle/providers/__init__.py +++ b/src/shelloracle/providers/__init__.py @@ -80,7 +80,6 @@ def _providers() -> dict[str, type[Provider]]: from shelloracle.providers.ollama import Ollama from shelloracle.providers.openai import OpenAI from shelloracle.providers.xai import XAI - from shelloracle.providers.google import Google return { Ollama.name: Ollama, From 49b451ff11bc2c2e0257499b250aa8c10956ce9a Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 8 Feb 2025 20:52:38 -0500 Subject: [PATCH 5/5] Update shelloracle.fish --- src/shelloracle/shell/shelloracle.fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shelloracle/shell/shelloracle.fish b/src/shelloracle/shell/shelloracle.fish index 2637fb6..46529dd 100644 --- a/src/shelloracle/shell/shelloracle.fish +++ b/src/shelloracle/shell/shelloracle.fish @@ -6,4 +6,4 @@ function __shelloracle__ commandline -r -- $output end -bind \cf __shelloracle__ \ No newline at end of file +bind \cf __shelloracle__