From e31e58c9c465e92496a8551296d6b879d2f1ce58 Mon Sep 17 00:00:00 2001 From: Heiko Joerg Schick Date: Wed, 9 Sep 2026 23:07:05 +0200 Subject: [PATCH] Move API credentials to .env and set gpt-5.6-luna model - Load OPENAI_API_KEY and OPENAI_BASE_URL from gitignored .env (repo-root, CWD-independent) - Fix command explanation to use the configured model instead of hardcoded gpt-4o-mini - Update default model and fallbacks to gpt-5.6-luna - Document .env-based configuration in READMEs --- .env.example | 1 + README.md | 26 ++++++++++++++++++++------ edison/README.md | 15 ++++++++++++--- edison/core/api_client.py | 34 +++++++++++++++++++++++++++++++--- edison/edison.yaml | 11 ++++++++--- edison/ui/console.py | 2 +- edison/ui/interactive.py | 7 ++++--- 7 files changed, 77 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index 00075c1..60967e1 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ OPENAI_API_KEY="your-openai-api-key-here" +OPENAI_BASE_URL="https://api.openai.com/v1" diff --git a/README.md b/README.md index fd65547..0d7aa0b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Arguments: --theme : Set syntax highlighting theme (e.g., monokai, github-dark) Current configuration per edison.yaml: -* Model : gpt-4o-mini +* Model : gpt-5.6-luna * Temperature : 0 * Max. Tokens : 500 * Safety : on @@ -64,13 +64,27 @@ cd command-assistant .\install_edison.bat ``` -## OpenAI API Key Configuration +## API Configuration -Configure your OpenAI API key using one of these methods: +Edison needs an API key (and, when using a proxy, a base URL) to talk to your model backend. The recommended way is a `.env` file in the repository root, which is gitignored and therefore never checked in: -- Environment variable: `export OPENAI_API_KEY=` (Linux/macOS) or `$env:OPENAI_API_KEY=""` (Windows) -- Create a file at `~/.openai.apikey` with just the key -- Add the key to the `edison.yaml` configuration file +```bash +# .env (do not commit this file) +OPENAI_API_KEY="your-api-key" +OPENAI_BASE_URL="https://api.openai.com/v1" +``` + +Copy `.env.example` to get started: + +```bash +cp .env.example .env +``` + +You can also provide the key using any of these methods (in order of precedence): + +- `OPENAI_API_KEY` / `OPENAI_BASE_URL` environment variables (or in `.env`) +- A file at `~/.openai.apikey` containing just the key +- The `openai_api_key` / `base_url` entries in `edison.yaml` ## Usage Examples diff --git a/edison/README.md b/edison/README.md index ee7ca08..00c34f2 100644 --- a/edison/README.md +++ b/edison/README.md @@ -80,7 +80,7 @@ The rich terminal UI provides: Edison can be configured via the `edison.yaml` file. Here's an example configuration: ```yaml -model: gpt-3.5-turbo +model: gpt-5.6-luna temperature: 0 max_tokens: 500 safety: true @@ -88,13 +88,22 @@ safety: true ## API Key -Edison requires an OpenAI API key to function. You can provide it in one of the following ways: +Edison requires an API key to function. The recommended way is a `.env` file in the repository root (gitignored, so not checked in): + +``` +OPENAI_API_KEY="your-api-key" +OPENAI_BASE_URL="https://api.openai.com/v1" +``` + +You can also provide it in one of the following ways: 1. Environment variable: `OPENAI_API_KEY="your-api-key"` -2. `.env` file in the same directory as the script +2. `.env` file in the repository root 3. `.openai.apikey` file in your home directory 4. In the `edison.yaml` configuration file: `openai_api_key: "your-api-key"` +When using a custom backend or proxy, set `OPENAI_BASE_URL` (or `base_url` in `edison.yaml`) to its endpoint. + ## License MIT diff --git a/edison/core/api_client.py b/edison/core/api_client.py index 53349ad..5da48b3 100644 --- a/edison/core/api_client.py +++ b/edison/core/api_client.py @@ -11,6 +11,31 @@ from edison.utils import logging_utils logger = logging.getLogger(__name__) +def get_env_path(): + """ + Get the path to the project's `.env` file, independent of the current working directory. + + Returns: + str: The absolute path to the `.env` file at the repository root. + """ + project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + return os.path.join(project_root, ".env") + +def get_base_url(config): + """ + Get the API base URL from the environment or config. + + Prefers the `OPENAI_BASE_URL` environment variable, falling back to the + `base_url` value in the provided config dictionary. + + Args: + config (dict): A dictionary containing configuration values. + + Returns: + str or None: The API base URL, or None if not set. + """ + return os.getenv("OPENAI_BASE_URL") or config.get("base_url") + def get_api_key(config): """ Get the OpenAI API key from various sources. @@ -26,7 +51,7 @@ def get_api_key(config): Returns: str: The OpenAI API key """ - dotenv.load_dotenv() + dotenv.load_dotenv(get_env_path()) # Method 1: Read API key from environment variable # The user can set their OpenAI API key by creating a ".env" file in the same @@ -68,6 +93,9 @@ def create_client(config): OpenAI: An initialized OpenAI client. """ api_key = get_api_key(config) + base_url = get_base_url(config) + if base_url: + return OpenAI(api_key=api_key, base_url=base_url) return OpenAI(api_key=api_key) def call_api(client, config, query): @@ -95,7 +123,7 @@ def call_api(client, config, query): try: # Use the modern API pattern response = client.chat.completions.create( - model=config.get("model", "gpt-4o-mini"), + model=config.get("model", "gpt-5.6-luna"), messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt} @@ -137,7 +165,7 @@ def call_api_streaming(client, config, query, callback): try: # Use the streaming API pattern response = client.chat.completions.create( - model=config.get("model", "gpt-4o-mini"), + model=config.get("model", "gpt-5.6-luna"), messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt} diff --git a/edison/edison.yaml b/edison/edison.yaml index 49331ec..c888e28 100644 --- a/edison/edison.yaml +++ b/edison/edison.yaml @@ -1,7 +1,10 @@ -model: gpt-4o-mini # Default model for command generation +model: gpt-5.6-luna # Default model for command generation temperature: 0 max_tokens: 500 +# API base URL (optional): Set via OPENAI_BASE_URL in .env, or uncomment below +# base_url: https://api.openai.com/v1 + # Safety: If set to False, commands returned from the AI will be run *without* prompting the user. safety: True @@ -27,5 +30,7 @@ ui: # Structured explanations: Generate more structured command explanations structured_explanations: True -# Open AI API Key (optional): The key can aso be provided via environment variable (OPENAI_API_KEY), .env, or ~/.openai.apikey file -openai_api_key: +# OpenAI API Key (optional): The key can be provided via .env (OPENAI_API_KEY), +# an environment variable, or the ~/.openai.apikey file. Prefer .env so the key +# is not checked into version control. +# openai_api_key: your-api-key-here diff --git a/edison/ui/console.py b/edison/ui/console.py index 7f6678a..cfd6c3e 100644 --- a/edison/ui/console.py +++ b/edison/ui/console.py @@ -132,7 +132,7 @@ def handle_command_execution(client, config, command, explain=False): use_rich = ui_config.get("rich_formatting", True) # Generate explanation - explanation = get_command_explanation(client, command, structured=structured) + explanation = get_command_explanation(client, command, structured=structured, model=config.get("model")) if use_rich: print() # Add a blank line before the explanation diff --git a/edison/ui/interactive.py b/edison/ui/interactive.py index 9504a4e..664db67 100644 --- a/edison/ui/interactive.py +++ b/edison/ui/interactive.py @@ -25,7 +25,7 @@ def handle_streaming_output_interactive(token): # Print the token without a newline and flush immediately print(token, end='', flush=True) -def get_command_explanation(client, command, structured=False): +def get_command_explanation(client, command, structured=False, model=None): """ Get an explanation for a command. @@ -33,6 +33,7 @@ def get_command_explanation(client, command, structured=False): client: The OpenAI client. command (str): The command to explain. structured (bool): Whether to use structured explanation format. + model (str): The model to use for explanation. Returns: str: The explanation of the command. @@ -61,7 +62,7 @@ def get_command_explanation(client, command, structured=False): """ response = client.chat.completions.create( - model="gpt-4o-mini", + model=model or "gpt-5.6-luna", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": command} @@ -284,7 +285,7 @@ def interactive_mode(client, config): use_rich = ui_config.get("rich_formatting", True) # Generate explanation - explanation = get_command_explanation(client, command, structured=structured) + explanation = get_command_explanation(client, command, structured=structured, model=config.get("model")) if use_rich: print() # Add a blank line before the explanation