Skip to content
Open
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
56 changes: 56 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Changelog

All notable changes to this project will be documented in this file.

## [1.0.2] - 2026-02-16

### Changed

#### API Migration: google.generativeai → google.genai
- **Deprecated Package Removed:** Migrated from `google-generativeai` (end-of-life) to `google-genai` (active support)
- The `google.generativeai` package is no longer receiving updates or bug fixes
- Switched to `google.genai` for ongoing maintenance and new features

#### Python Version Requirements
- **Minimum Python:** Updated from `>=3.8` to `>=3.14`
- Aligns with current `google-genai` package requirements
- Ensures compatibility with latest dependency versions

#### Code Updates

**Package Dependencies:**
- Replaced `google-generativeai` with `google-genai` in `pyproject.toml`
- Updated `requires-python` constraint to `>=3.14`

**API Implementation Changes:**

1. **Client Initialization**
- Old: `genai.configure(api_key=api_key)`
- New: `client = genai.Client(api_key=api_key)`

2. **Model API Calls**
- Old:
```python
model = genai.GenerativeModel(MODEL_NAME)
response = model.generate_content(prompt, request_options={"timeout": TIMEOUT})
```
- New:
```python
client = genai.Client(api_key=api_key)
response = client.models.generate_content(model=MODEL_NAME, contents=prompt)
```

3. **Exception Handling**
- Old: Caught specific exception types (`genai.types.BlockedPromptException`, `genai.types.StopCandidateException`)
- New: Generic exception handling with string-based filtering for "blocked" and "stopped" messages
- Rationale: New API uses different exception hierarchy

### Deprecated

- `google.generativeai` package (no longer maintained)

### Notes

- All functionality preserved with new API
- No breaking changes to user-facing CLI interface
- Improved long-term maintainability with active dependency support
21 changes: 11 additions & 10 deletions how/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import os
import threading
import time
import google.generativeai as genai
from google import genai
from google.genai import types
import getpass
import platform
import pyperclip
Expand All @@ -19,7 +20,7 @@
CONFIG_DIR = os.path.expanduser("~/.how-cli")
API_KEY_FILE = os.path.join(CONFIG_DIR, ".google_api_key")
HISTORY_FILE = os.path.join(CONFIG_DIR, "history.log")
MODEL_NAME = os.getenv("HOW_MODEL", "models/gemini-2.5-flash-lite")
MODEL_NAME = os.getenv("HOW_MODEL", "models/gemini-3-flash-preview")


class ApiError(Exception): pass
Expand All @@ -32,7 +33,8 @@ def header():
" __ \n"
" / / ___ _ __\n"
" / _ \\/ _ \\ |/|/ /\n"
"/_//_/\\___/__,__/ \n"
"/_//_/\\___/__4__/ \n"
"\n"
)
print("Ask me how to do anything in your terminal!")

Expand Down Expand Up @@ -124,8 +126,7 @@ def get_or_create_api_key(force_reenter=False) -> str:


def generate_response(api_key: str, prompt: str, silent: bool=False, max_retries: int=3) -> str:
genai.configure(api_key=api_key)
model = genai.GenerativeModel(MODEL_NAME)
client = genai.Client(api_key=api_key)
stop_event = threading.Event()
spinner_thread = None
if not silent:
Expand All @@ -137,16 +138,16 @@ def generate_response(api_key: str, prompt: str, silent: bool=False, max_retries
for attempt in range(max_retries):
try:
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(model.generate_content, prompt, request_options={"timeout": TIMEOUT})
future = executor.submit(client.models.generate_content, model=MODEL_NAME, contents=prompt)
response = future.result(timeout=TIMEOUT+5)
text = (response.text or "").strip()
if not text:
if getattr(response, "prompt_feedback", None) and getattr(response.prompt_feedback, "block_reason", None):
raise ContentError(f"Blocked: {response.prompt_feedback.block_reason.name}")
raise ContentError("Empty response from API.")
return text
except (genai.types.BlockedPromptException, genai.types.StopCandidateException) as e:
raise ContentError("Content blocked or stopped early.") from e
except Exception as e:
if "blocked" in str(e).lower() or "stopped" in str(e).lower():
raise ContentError("Content blocked or stopped early.") from e
raise
except concurrent.futures.TimeoutError:
if attempt == max_retries-1: raise ApiTimeoutError("API request timed out.")
time.sleep(2**attempt)
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ build-backend = "setuptools.build_meta"

[project]
name = "how-cli-assist"
version = "1.0.1"
version = "1.0.2"
description = "Terminal assistant generating accurate shell commands"
authors = [{ name = "Adem Kouki" }]
authors = [{ name = "Adem Kouki. Updates by Hypothesis Factory" }]
readme = "README.md"
license = {text = "MIT"}
dependencies = [
"google-generativeai",
"google-genai",
"pyperclip",
"psutil"
]
requires-python = ">=3.8"
requires-python = ">=3.10"

[project.scripts]
how = "how.main:main"