Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/release_notes/v0.1.6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## v0.1.6 — Fix venv activation persistence & add Gemini retry/backoff

### Summary
This release fixes two user-facing issues discovered when running the CLI on Arch Linux:

- Ensure suggested environment steps (like `python -m venv .venv` + `source .venv/bin/activate`) are executed in the same shell so activation persists and subsequent commands (e.g., `pip install`) run inside the created venv instead of the system Python.
- Add a small retry + exponential backoff around the Google Gemini client call to handle transient 503 “model overloaded” errors before falling back to the REST path.

These changes reduce the PEP 668 (“externally-managed-environment”) errors on Arch and make the AI backend more resilient to temporary model overloads.

### Notable changes
- `src/ai_pkg/cli.py` — run all `env_steps` together in one bash shell and add failure handling.
- `src/ai_pkg/backends.py` — retry transient 503 server errors with exponential backoff before falling back to REST.

### Upgrade / usage notes
- Prefer running inside a virtual environment and install in editable mode for development:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .
```
- Ensure `GEMINI_API_KEY` is set or pass `--api-key` to the CLI.

### Files changed
- `src/ai_pkg/cli.py`
- `src/ai_pkg/backends.py`

---
Generated release notes by the maintainer tooling.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="https://raw.githubusercontent.com/rohankrsingh/ai-pkg/main/.github/assets/banner.png" alt="AI-PKG Banner" width="600"/>
</p>

<h1 align="center">🌟 ai-pkg</h1>
<h1 align="center">ai-pkg</h1>

<p align="center">
<strong>Your AI-Powered Development Environment Wizard for Arch Linux</strong>
Expand Down
48 changes: 37 additions & 11 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,12 @@ if ! command -v pacman &>/dev/null; then
fi

echo "Choose installation method:"
echo "1) pipx (PyPI)"
echo "2) yay (AUR binary)"
echo "1) yay (AUR binary)"
echo "2) Local virtualenv (installs a venv under ~/.local/ai-pkg and a shim in ~/.local/bin)"
read -rp "Enter 1 or 2: " choice

if [[ "$choice" == "1" ]]; then
if ! command -v pipx &>/dev/null; then
echo "⚙️ Installing pipx..."
sudo pacman -S --noconfirm python-pipx
pipx ensurepath || true
fi
echo "⚙️ Installing via pipx..."
pipx install ai-pkg || pipx upgrade ai-pkg || echo "pipx install failed"
echo "✅ Installed ai-pkg via pipx"
elif [[ "$choice" == "2" ]]; then
# AUR install via yay
if ! command -v yay &>/dev/null; then
echo "⚙️ Installing yay (AUR helper)..."
sudo pacman -S --needed --noconfirm git base-devel
Expand All @@ -34,6 +26,40 @@ elif [[ "$choice" == "2" ]]; then
echo "⚙️ Installing ai-pkg-bin via yay..."
yay -S --noconfirm ai-pkg-bin || echo "AUR install failed"
echo "✅ Installed ai-pkg-bin from AUR"
elif [[ "$choice" == "2" ]]; then
# Local venv install
echo "⚙️ Installing into a local virtualenv..."

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
venv_dir="$HOME/.local/ai-pkg/.venv"
bin_shim="$HOME/.local/bin/ai-pkg"

mkdir -p "$(dirname "$bin_shim")"
mkdir -p "$(dirname "$venv_dir")"

echo "Creating virtualenv at $venv_dir"
python -m venv "$venv_dir"

echo "Installing package in editable mode into the venv"
# Use the venv's pip to install the package from the repository root
"$venv_dir/bin/python" -m pip install --upgrade pip
"$venv_dir/bin/python" -m pip install -e "$repo_root" || {
echo "❌ Failed to install package into venv"
exit 1
}

# Create a small shim so `ai-pkg` is available on PATH via ~/.local/bin
cat > "$bin_shim" <<EOF
#!/usr/bin/env bash
exec "$venv_dir/bin/ai-pkg" "\$@"
EOF
chmod +x "$bin_shim"

echo "✅ Installed ai-pkg into local venv: $venv_dir"
echo "Shim created at: $bin_shim"
if ! command -v ai-pkg &>/dev/null; then
echo "Note: ensure ~/.local/bin is in your PATH to run 'ai-pkg' directly."
fi
else
echo "❌ Invalid choice. Exiting."
exit 1
Expand Down
18 changes: 16 additions & 2 deletions src/ai_pkg/backends.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import logging
import time
import requests
from typing import Any

Expand Down Expand Up @@ -62,14 +63,27 @@ def suggest_with_gemini(goal: str, api_key: str):
"IMPORTANT: Respond with ONLY valid JSON (no markdown, no surrounding backticks). The JSON must be either an object with keys 'packages' and 'env_steps', or simply an array of package names (legacy).\n"
f"Task: Provide environment steps and packages needed to {goal}\n"
"Prefer pacman package names and keep commands idempotent. When suggesting pacman install commands, prefer using '--needed' so packages already installed are skipped.\n"
"Example object response: {\"env_steps\": [\"python -m venv .venv\", \". .venv/bin/activate\"], \"packages\": [\"python\", \"git\"] }\n"
"Example object response: {\"env_steps\": [\"python -m venv .venv\", \"source .venv/bin/activate\"], \"packages\": [\"python\", \"git\"] }\n"
)

# Prefer using google-genai client
if genai is not None:
try:
client = genai.Client()
response = client.models.generate_content(model="gemini-2.5-flash", contents=prompt_text)
# Retry a few times for transient server-side overloads (503).
max_attempts = 3
response = None
for attempt in range(1, max_attempts + 1):
try:
response = client.models.generate_content(model="gemini-2.5-flash", contents=prompt_text)
break
except Exception as e:
# If this was the last attempt, re-raise to be caught by outer except
if attempt == max_attempts:
raise
logger.warning('Gemini client transient error (attempt %d/%d): %s', attempt, max_attempts, e)
# exponential backoff
time.sleep(2 ** (attempt - 1))
# Attempt to extract text safely from likely response shapes
text = None
try:
Expand Down
18 changes: 14 additions & 4 deletions src/ai_pkg/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,20 @@ def main(
if not should_run_env:
# Ask the user interactively
should_run_env = typer.confirm("Run the environment setup steps now?")
if should_run_env:
for cmd in env_steps:
typer.secho(f"Running: {cmd}", fg=typer.colors.YELLOW)
subprocess.run(cmd, shell=True)
if should_run_env:
# Run environment setup steps in a single shell session so that
# activation (source / .) affects subsequent commands. When we
# previously ran each command in its own subprocess, the venv
# activation didn't persist and later `pip install` used the
# system pip (triggering PEP 668 'externally-managed-environment').
# Combining with && preserves state across the sequence.
combined = " && ".join(env_steps)
typer.secho(f"Running: {combined}", fg=typer.colors.YELLOW)
# Use bash so `source` and `.` are available as builtins.
result = subprocess.run(combined, shell=True, executable="/bin/bash")
if result.returncode != 0:
typer.secho("❌ Environment setup steps failed.", fg=typer.colors.RED)
raise typer.Exit(result.returncode)

if dry_run:
core.install_packages(pkgs, dry_run=True, auto_yes=yes, aur_helper=aur_helper_final)
Expand Down