Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0d75887
Voice feature: speeech-to-text added
Sahilbhatane Dec 31, 2025
802531b
test fixs
Sahilbhatane Dec 31, 2025
30e2364
chore: add myenv and venv312 to gitignore
Sahilbhatane Dec 31, 2025
6a3ecc2
fix: remove myenv from repo and fix voice test mocking
Sahilbhatane Dec 31, 2025
c726440
docs: remove tiny.en model, use base.en as default everywhere
Sahilbhatane Dec 31, 2025
2fd7045
fix: address Copilot and CodeRabbit review comments
Sahilbhatane Dec 31, 2025
c306666
fix: skip voice tests when numpy not installed (optional dep)
Sahilbhatane Dec 31, 2025
e06af1f
Suggestion fix and import fix
Sahilbhatane Jan 11, 2026
1a5b848
System requirements for voice and key detection
Sahilbhatane Jan 11, 2026
bbd17de
test fix for py 3.11
Sahilbhatane Jan 11, 2026
df5ba0c
Test fix and consolidate package installations
Sahilbhatane Jan 12, 2026
b36929a
fix: Code review fixes for PR #405
Sahilbhatane Jan 12, 2026
320a2ef
fix: SonarCloud reliability improvements
Sahilbhatane Jan 12, 2026
4afe7f6
refactor: Reduce cognitive complexity in install method
Sahilbhatane Jan 12, 2026
e198b23
fix: Remove unused variable and clean imports in voice.py
Sahilbhatane Jan 12, 2026
54a6fed
test: Clean up unused variables in test_voice.py
Sahilbhatane Jan 12, 2026
e4f6589
fix: Extract 'Installation failed' constant to eliminate duplication
Sahilbhatane Jan 12, 2026
71c3593
fix: Remove unused fixture parameters from voice tests
Sahilbhatane Jan 12, 2026
297dec1
black formating for lint
Sahilbhatane Jan 12, 2026
0ae0601
fix: VoiceInputHandler resources are always cleaned up
Sahilbhatane Jan 12, 2026
4cb6fd9
user choice for model selection
Sahilbhatane Jan 12, 2026
2ac9a4a
suggstion fix
Sahilbhatane Jan 12, 2026
4e75558
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 19, 2026
a390c44
Conversation fixs
Sahilbhatane Jan 19, 2026
4c4ff26
test fix and address some other issues
Sahilbhatane Jan 19, 2026
16d07b6
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 19, 2026
c0fb0f1
Test fix
Sahilbhatane Jan 19, 2026
23143c2
Choice change fix
Sahilbhatane Jan 19, 2026
286d1c9
API fix
Sahilbhatane Jan 19, 2026
103e0b8
Delet files and add proper instructions
Sahilbhatane Jan 19, 2026
2f9fc10
CLI notes for end user
Sahilbhatane Jan 19, 2026
6d04f39
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 19, 2026
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
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ __pycache__/
*.py[cod]
*$py.class
*.so

# ==============================
# Virtual Environments
# ==============================
env/
venv/
myenv/
venv312/
ENV/
env.bak/
venv.bak/
.venv/

# ==============================
# Distribution / Packaging
# ==============================
.Python
build/
develop-eggs/
Expand All @@ -37,7 +53,11 @@ wheels/
*.egg
.venv/
venv/
myenv/
venv312/
ENV/
env.bak/
venv.bak/

# IDE
.idea/
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ cortex install "tools for video compression"
| Feature | Description |
|---------|-------------|
| **Natural Language** | Describe what you need in plain English |
| **Voice Input** | Hands-free mode with Whisper speech recognition ([F9 to speak](docs/VOICE_INPUT.md)) |
| **Dry-Run Default** | Preview all commands before execution |
| **Sandboxed Execution** | Commands run in Firejail isolation |
| **Full Rollback** | Undo any installation with `cortex rollback` |
Expand Down
39 changes: 29 additions & 10 deletions cortex/api_key_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,24 +125,21 @@ def detect(self) -> tuple[bool, str | None, str | None, str | None]:
def _check_environment_api_keys(self) -> tuple[bool, str, str, str] | None:
"""Check for API keys in environment variables.

Respects CORTEX_PROVIDER setting when multiple keys are available.
Falls back to OpenAI if Anthropic is not available but OpenAI is.
Respects CORTEX_PROVIDER when multiple keys exist and prefers OpenAI when unspecified.
"""
# Check if user has explicit provider preference
preferred_provider = os.environ.get("CORTEX_PROVIDER", "").lower()
explicit_provider = os.environ.get("CORTEX_PROVIDER", "").lower()

# If provider is specified, check for that key first
if preferred_provider in ("anthropic", "claude"):
# If user explicitly set a provider, check that key first
if explicit_provider in ("anthropic", "claude"):
value = os.environ.get("ANTHROPIC_API_KEY")
if value:
return (True, value, "anthropic", "environment")
elif preferred_provider == "openai":
elif explicit_provider == "openai":
value = os.environ.get("OPENAI_API_KEY")
if value:
return (True, value, "openai", "environment")

# Fall back to checking all keys if no preference or preferred key not found
# Prefer OpenAI over Anthropic if no explicit preference (since Anthropic seems to have issues)
# Fallback: prefer OpenAI first, then Anthropic
for env_var, provider in [("OPENAI_API_KEY", "openai"), ("ANTHROPIC_API_KEY", "anthropic")]:
value = os.environ.get(env_var)
if value:
Expand All @@ -160,7 +157,29 @@ def _check_encrypted_storage(self) -> tuple[bool, str, str, str] | None:

env_mgr = get_env_manager()

# Check for API keys in encrypted storage
# If CORTEX_PROVIDER is explicitly set, check that provider's key first
explicit_provider = os.environ.get("CORTEX_PROVIDER", "").lower()
if explicit_provider in ["openai", "claude", "anthropic"]:
# Map provider names to env vars and canonical provider names
if explicit_provider == "openai":
target_env_var = "OPENAI_API_KEY"
target_provider = "openai"
else: # claude or anthropic both map to ANTHROPIC_API_KEY
target_env_var = "ANTHROPIC_API_KEY"
target_provider = "anthropic"

value = env_mgr.get_variable(app="cortex", key=target_env_var, decrypt=True)
if value:
os.environ[target_env_var] = value
logger.debug(f"Loaded {target_env_var} from encrypted storage")
return (
True,
value,
target_provider,
"encrypted storage (~/.cortex/environments/)",
)

# Check for API keys in encrypted storage (default order)
for env_var, provider in ENV_VAR_PROVIDERS.items():
value = env_mgr.get_variable(app="cortex", key=env_var, decrypt=True)
if value:
Expand Down
37 changes: 26 additions & 11 deletions cortex/branding.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
- Consistent visual language
"""

import sys

from rich import box
from rich.console import Console
from rich.panel import Panel
from rich.table import Table

console = Console()
# Use force_terminal for consistent styled output across environments
console = Console(force_terminal=True)

# Brand colors
CORTEX_CYAN = "cyan"
Expand Down Expand Up @@ -70,13 +73,23 @@ def cx_print(message: str, status: str = "info"):
"""
badge = "[bold white on dark_cyan] CX [/bold white on dark_cyan]"

status_icons = {
"info": "[dim]│[/dim]",
"success": "[green]✓[/green]",
"warning": "[yellow]⚠[/yellow]",
"error": "[red]✗[/red]",
"thinking": "[cyan]⠋[/cyan]", # Spinner frame
}
# Use ASCII-only icons on Windows for better compatibility
if sys.platform == "win32":
status_icons = {
"info": "[dim]|[/dim]",
"success": "[green]+[/green]",
"warning": "[yellow]![/yellow]",
"error": "[red]x[/red]",
"thinking": "[cyan]*[/cyan]",
}
else:
status_icons = {
"info": "[dim]│[/dim]",
"success": "[green]✓[/green]",
"warning": "[yellow]⚠[/yellow]",
"error": "[red]✗[/red]",
"thinking": "[cyan]⠋[/cyan]", # Spinner frame
}

icon = status_icons.get(status, status_icons["info"])
console.print(f"{badge} {icon} {message}")
Expand All @@ -86,18 +99,20 @@ def cx_step(step_num: int, total: int, message: str):
"""
Print a numbered step with the CX badge.

Example: CX [1/4] Updating package lists...
Example: CX | [1/4] Updating package lists...
"""
badge = "[bold white on dark_cyan] CX [/bold white on dark_cyan]"
console.print(f"{badge} [dim]│[/dim] [{step_num}/{total}] {message}")
separator = "|" if sys.platform == "win32" else "│"
console.print(f"{badge} [dim]{separator}[/dim] [{step_num}/{total}] {message}")


def cx_header(title: str):
"""
Print a section header.
"""
console.print()
console.print(f"[bold cyan]━━━ {title} ━━━[/bold cyan]")
separator = "---" if sys.platform == "win32" else "━━━"
console.print(f"[bold cyan]{separator} {title} {separator}[/bold cyan]")
console.print()


Expand Down
Loading