Skip to content

Conversation

@jaysurse
Copy link
Collaborator

@jaysurse jaysurse commented Dec 24, 2025

Closes #355

Summary
This PR fixes an issue where cortex wizard exits early even when a valid API key is already exported in the environment.

Currently, the wizard always prompts the user to export an API key and stops execution. This change updates the wizard logic to detect existing API keys and continue the setup flow instead of exiting.

What was fixed
Detect API keys already present in environment variables
Prevent premature wizard exit when a key exists
Allow the wizard to continue setup (provider confirmation / next steps)
Improve first-time user experience

Summary by CodeRabbit

  • New Features

    • Provider-aware install flow with per-provider probing and API-key resolution
    • Enhanced first-run wizard: provider detection, API-key validation, dry-run verification, non‑interactive mode
    • New env command for comprehensive environment variable management
    • Added notify command to CLI
    • API key validation utilities for Anthropic and OpenAI
  • Documentation

    • Updated example .env with sample API key entries
  • Tests

    • Expanded/updated tests (parallel installs, env-driven CLI, async LLM router); tests now invoke python3 where applicable

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 24, 2025

📝 Walkthrough

Walkthrough

Adds provider-aware CLI and wizard flows with per-provider API-key detection/validation, multi-provider probing for install (sequential/optional parallel), new env and notify subcommands, expanded .env discovery, API-key validators, and corresponding test/example updates.

Changes

Cohort / File(s) Summary
CLI core
cortex/cli.py
Added _get_api_key_for_provider(), made _get_api_key() provider-aware, refactored install(...) to probe per-provider plans (forced_provider, sequential/parallel execution), and added notify() and env(args) CLI surfaces.
First-run wizard & state
cortex/first_run_wizard.py
New FirstRunWizard, provider detection, env-backed API-key helpers, dry-run verification, WizardState/WizardStep/StepResult exports, non-interactive handling and centralized wizard flow.
API key validation utilities
cortex/utils/api_key_validator.py
New validators validate_anthropic_api_key() and validate_openai_api_key() performing lightweight HTTP checks.
Env discovery & example
cortex/env_loader.py, .env.example
Expanded .env search order (cwd, parent, package dir if present, home, /etc guarded by POSIX), graceful missing-package handling, and updated example env (contains sample keys / conflict markers).
Installer tests
tests/installer/test_parallel_install.py
Switched test subprocess invocations from python to python3.
CLI tests
tests/test_cli.py, tests/test_cli_extended.py
Tests reworked to be environment-driven (OPENAI_API_KEY, CORTEX_PROVIDER) and to patch CommandInterpreter/Coordinator rather than internal key-resolution; adjusted dry-run/execute expectations.
Wizard tests
tests/test_first_run_wizard.py
Added/renamed tests for provider-specific env keys (Anthropic/OpenAI), removed one integration flow test.
Env manager tests
tests/test_env_manager.py
Adjusted accepted URL scheme in a test from sftp to ftp.
Examples & demos
examples/env_demo.py, examples/parallel_llm_demo.py
Minor import/formatting tweaks; one duplicate await added in demo (runs twice but result overwritten).
New parallel-LLM test script
test_parallel_llm.py
New script exercising parallel LLMRouter flows and helper utilities (multiple async tests).
Minor formatting
cortex/dependency_check.py, cortex/llm/interpreter.py
Small formatting changes (quoting/join style and line breaks) with no functional change.

Sequence Diagram(s)

mermaid
sequenceDiagram
autonumber
participant User
participant CLI as CortexCLI
participant Env as EnvLoader
participant Wizard as FirstRunWizard
participant Interpreter as CommandInterpreter
participant Provider as LLM Provider
rect rgba(220,235,255,0.14)
Note over CLI,Env: Startup — detect providers & keys
end
User->>CLI: run "cortex wizard" / "cortex install"
CLI->>Env: detect_available_providers()
Env-->>CLI: providers list (ollama/openai/anthropic)
alt wizard
CLI->>Wizard: FirstRunWizard(interactive=True).run()
Wizard->>Env: read/save API key
Wizard->>Provider: validate API key (dry-run)
Provider-->>Wizard: validation result
Wizard-->>CLI: exit code
else install
CLI->>CLI: for provider in ordered_providers
CLI->>Interpreter: probe/build plan (provider-specific)
Interpreter-->>CLI: commands / none
alt commands produced
CLI->>Interpreter: execute (seq or parallel)
Interpreter->>Provider: run install actions
Provider-->>Interpreter: progress / results
Interpreter-->>CLI: execution outcome
else no commands
CLI-->>User: error (no provider produced commands)
end
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • mikejmorgan-ai
  • Suyashd999

Poem

🐇 Hop, hop, the keys align tonight,

Providers checked by soft moonlight.
Env files snug and flags all set,
Wizard hums, plans cast and met—
Install hops on, and all is right. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.07% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes check ❓ Inconclusive Multiple ancillary changes are present beyond fixing the core wizard flow issue, including new modules (api_key_validator.py), test updates, example file modifications with merge conflicts, and unrelated demo script changes that lack clear justification. Review and justify the scope of supporting changes such as api_key_validator.py, demo script modifications, and merge conflict markers in .env.example. Consider if all changes are necessary for fixing the wizard issue or should be addressed separately.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: allowing the wizard to continue when an API key is already present in the environment, which is the primary objective of this PR.
Description check ✅ Passed The description provides a clear summary of the fix, explains what was changed, and relates to the linked issue #355. While the Checklist items are not marked, the required sections (Related Issue, Summary) are present and substantive.
Linked Issues check ✅ Passed The code changes comprehensively address the requirements from issue #355: detecting API keys in environment variables (via updated wizard and env_loader), preventing premature wizard exit, and allowing the setup flow to continue with provider selection and confirmation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jaysurse jaysurse force-pushed the fix/cortex-wizard-not-running branch from 71f1fe9 to 53a9e46 Compare December 24, 2025 06:57
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cortex/first_run_wizard.py (1)

329-329: Fix Anthropic API key format validation at line 329.

The validation currently checks only for startswith("sk-"), but Anthropic API keys specifically use the format "sk-ant-api03-". The current validation is too permissive and would incorrectly accept OpenAI keys (which also start with "sk-") as valid Claude keys. Update the validation at line 329 to check for the correct Anthropic prefix.

🧹 Nitpick comments (2)
cortex/first_run_wizard.py (1)

262-268: Consider validating existing API keys before using them.

The code detects existing API keys in the environment but doesn't validate that they're actually valid or properly formatted. Invalid or expired keys would cause runtime failures later when attempting API calls.

💡 Suggested validation approach

You could add a helper method to validate keys before accepting them:

def _validate_existing_key(self, key: str, provider: str) -> bool:
    """Validate that an existing API key has the correct format."""
    if not key:
        return False
    # Basic format checks
    if provider == "anthropic" and not key.startswith("sk-ant-"):
        return False
    if provider == "openai" and not key.startswith("sk-"):
        return False
    return True

Then use it when checking:

 # Check for existing API keys
 existing_claude = os.environ.get("ANTHROPIC_API_KEY")
+existing_claude = existing_claude if self._validate_existing_key(existing_claude, "anthropic") else None
 existing_openai = os.environ.get("OPENAI_API_KEY")
+existing_openai = existing_openai if self._validate_existing_key(existing_openai, "openai") else None
cortex/cli.py (1)

773-773: Minor unrelated change: notify command added to help.

This line adds the notify command to the help table, which is unrelated to the main PR objective of fixing the wizard's API key detection. While the change itself is fine, it's generally better practice to keep PRs focused on a single objective.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33cdf7d and 53a9e46.

📒 Files selected for processing (2)
  • cortex/cli.py
  • cortex/first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
  • cortex/first_run_wizard.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Package
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (6)
cortex/first_run_wizard.py (4)

271-278: LGTM! Clear visual feedback for existing keys.

The status indicators provide clear feedback to users about which API keys are already configured, improving the user experience.


281-295: Non-interactive auto-selection logic is correct.

The logic appropriately auto-selects the provider when an API key is detected in non-interactive mode, which aligns with the PR objectives. The same validation concern mentioned earlier applies here as well.


301-305: LGTM! Streamlined flow for existing Claude keys.

The code correctly skips the API key prompt when a key is already present in the environment, improving the user experience as intended by the PR.


308-312: LGTM! Consistent handling for existing OpenAI keys.

The implementation mirrors the Claude key handling and correctly uses existing keys from the environment.

cortex/cli.py (2)

12-12: LGTM! Clean import.

The import statement is properly placed and follows Python conventions.


745-748: LGTM! Clean integration with FirstRunWizard.

The wizard command properly instantiates and runs the FirstRunWizard, which now includes the enhanced API key detection logic. The implementation is straightforward and returns appropriate exit codes.

@jaysurse
Copy link
Collaborator Author

working on it - effiti

@Suyashd999 Suyashd999 self-requested a review December 24, 2025 07:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cortex/env_loader.py (1)

18-23: Remove duplicate imports.

Lines 22-23 duplicate the imports from lines 18-19, causing redefinition errors flagged by Ruff (F811) and breaking import formatting (I001).

🔎 Proposed fix
 import os
 from pathlib import Path
 

-import os
-from pathlib import Path
-
 def get_env_file_locations() -> list[Path]:
🧹 Nitpick comments (4)
cortex/first_run_wizard.py (4)

17-32: Fix import organization.

The import block is flagged as unsorted/unformatted by Ruff (I001). Organize imports per PEP 8: standard library, then third-party, then local imports, each group alphabetically sorted.

🔎 Example organization
# Standard library
import json
import logging
import os
import random
import shutil
import subprocess
import sys
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any

# Local imports
from cortex.utils.api_key_test import test_anthropic_api_key, test_openai_api_key

325-349: Consider adding retry limit to prevent infinite loops.

The while loop at line 329 will continue indefinitely if the user repeatedly enters invalid keys. Consider adding a retry limit to prevent frustration.

🔎 Proposed improvement
         # Validate and prompt for key lazily
         if provider == "anthropic":
             key = os.environ.get("ANTHROPIC_API_KEY")
             if key:
                 key = key.strip()
+            max_attempts = 3
+            attempts = 0
-            while not key or not key.startswith("sk-ant-"):
+            while (not key or not key.startswith("sk-ant-")) and attempts < max_attempts:
+                attempts += 1
                 print("\nNo valid Anthropic API key found.")
                 key = self._prompt("Enter your Claude (Anthropic) API key: ")
                 if key and key.startswith("sk-ant-"):
                     self._save_env_var("ANTHROPIC_API_KEY", key)
                     os.environ["ANTHROPIC_API_KEY"] = key
+            if not key or not key.startswith("sk-ant-"):
+                print("\n❌ Failed to configure valid API key after multiple attempts.")
+                return False

Apply similar logic to the OpenAI validation loop at line 354.


465-465: Remove redundant import.

The test_anthropic_api_key function is already imported at the module level (line 32). This inline import on line 465 is unnecessary.

🔎 Proposed fix
                 if self.interactive:
                     do_test = self._prompt("Would you like to test your Claude API key now? [Y/n]: ", default="y")
                     if do_test.strip().lower() in ("", "y", "yes"):
                         print("\nTesting Claude API key...")
-                        from cortex.utils.api_key_test import test_anthropic_api_key
                         if test_anthropic_api_key(existing_claude):

Apply the same fix for line 485 (test_openai_api_key).


765-765: Remove trailing whitespace.

Line 765 contains trailing whitespace, flagged by Ruff (W293).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53a9e46 and 97d27b0.

📒 Files selected for processing (4)
  • cortex/cli.py
  • cortex/env_loader.py
  • cortex/first_run_wizard.py
  • cortex/utils/api_key_test.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/utils/api_key_test.py
  • cortex/cli.py
  • cortex/first_run_wizard.py
  • cortex/env_loader.py
🧬 Code graph analysis (2)
cortex/cli.py (1)
cortex/first_run_wizard.py (2)
  • needs_setup (246-248)
  • run (284-385)
cortex/first_run_wizard.py (1)
cortex/utils/api_key_test.py (2)
  • test_anthropic_api_key (4-28)
  • test_openai_api_key (30-52)
🪛 GitHub Actions: CI
cortex/env_loader.py

[error] 18-18: I001 Import block is un-sorted or un-formatted

🪛 GitHub Check: lint
cortex/utils/api_key_test.py

[failure] 1-2: Ruff (I001)
cortex/utils/api_key_test.py:1:1: I001 Import block is un-sorted or un-formatted

cortex/first_run_wizard.py

[failure] 17-32: Ruff (I001)
cortex/first_run_wizard.py:17:1: I001 Import block is un-sorted or un-formatted


[failure] 2-3: Ruff (I001)
cortex/first_run_wizard.py:2:5: I001 Import block is un-sorted or un-formatted


[failure] 765-765: Ruff (W293)
cortex/first_run_wizard.py:765:1: W293 Blank line contains whitespace

cortex/env_loader.py

[failure] 23-23: Ruff (F811)
cortex/env_loader.py:23:21: F811 Redefinition of unused Path from line 19


[failure] 22-22: Ruff (F811)
cortex/env_loader.py:22:8: F811 Redefinition of unused os from line 18

🪛 GitHub Check: Lint
cortex/utils/api_key_test.py

[failure] 1-2: Ruff (I001)
cortex/utils/api_key_test.py:1:1: I001 Import block is un-sorted or un-formatted

cortex/first_run_wizard.py

[failure] 17-32: Ruff (I001)
cortex/first_run_wizard.py:17:1: I001 Import block is un-sorted or un-formatted


[failure] 2-3: Ruff (I001)
cortex/first_run_wizard.py:2:5: I001 Import block is un-sorted or un-formatted


[failure] 765-765: Ruff (W293)
cortex/first_run_wizard.py:765:1: W293 Blank line contains whitespace

cortex/env_loader.py

[failure] 23-23: Ruff (F811)
cortex/env_loader.py:23:21: F811 Redefinition of unused Path from line 19


[failure] 22-22: Ruff (F811)
cortex/env_loader.py:22:8: F811 Redefinition of unused os from line 18

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
🔇 Additional comments (10)
cortex/env_loader.py (1)

44-50: LGTM: Graceful handling of missing cortex package.

The try/except block appropriately handles the case where the cortex package may not be importable, allowing the env loader to work in various installation contexts.

cortex/cli.py (6)

63-78: LGTM: Well-designed fallback logic.

The refactored API key retrieval appropriately handles multiple scenarios: provider-specific keys, completed setup without keys (Ollama fallback), and unconfigured systems (helpful guidance). This aligns well with the PR objective to improve the wizard experience.


312-318: LGTM: Appropriate parameter addition.

The forced_provider parameter enables the wizard to test specific providers during onboarding, supporting the PR's goal of improving setup flow.


339-384: LGTM: Solid multi-provider fallback implementation.

The refactored install flow appropriately tries multiple providers with graceful fallback. The debug logging helps troubleshoot provider selection issues, and the error handling correctly continues to the next provider on failure.


770-777: LGTM: Clean wizard integration.

The wizard method properly integrates with FirstRunWizard and returns appropriate exit codes.


802-802: LGTM: Help documentation updated.

The notify command is appropriately added to the help table.


47-61: Add comprehensive docstring.

Per coding guidelines, public methods require complete docstrings with Args and Returns sections.

🔎 Proposed docstring
     def _get_api_key_for_provider(self, provider: str) -> str | None:
-        """Get API key for a specific provider."""
+        """Get API key for a specific provider.
+        
+        Args:
+            provider: Provider name ('ollama', 'fake', 'claude', 'openai').
+            
+        Returns:
+            API key string if found and valid, None otherwise.
+            Returns 'ollama-local' for ollama and 'fake-key' for fake provider.
+        """

Based on coding guidelines.

Likely an incorrect or invalid review comment.

cortex/first_run_wizard.py (2)

214-217: Add docstring.

Per coding guidelines, all methods should have docstrings describing their purpose.

🔎 Proposed addition
     def _setup_ollama(self) -> StepResult:
+        """Configure Ollama as the AI provider (no API key required).
+        
+        Returns:
+            StepResult indicating success with provider set to 'ollama'.
+        """
         print("\nOllama selected. No API key required.")

Based on coding guidelines.

Likely an incorrect or invalid review comment.


142-161: Add docstring and consider making package list configurable.

This method lacks a docstring entirely, violating coding guidelines. Additionally, the hardcoded package list may not suit all use cases.

🔎 Proposed improvements
-    def _install_suggested_packages(self):
-        """Offer to install suggested packages and run the install if user agrees."""
+    def _install_suggested_packages(self, suggestions: list[str] | None = None):
+        """Offer to install suggested packages and run the install if user agrees.
+        
+        Args:
+            suggestions: List of package names to suggest. Defaults to basic packages.
+        """
+        if suggestions is None:
+            suggestions = ["python", "numpy", "requests"]
-        suggestions = ["python", "numpy", "requests"]

Based on coding guidelines.

Likely an incorrect or invalid review comment.

cortex/utils/api_key_test.py (1)

38-38: gpt-3.5-turbo remains valid for API tests.

Confirmed: "gpt-3.5-turbo" is still a valid and functional model in the OpenAI API as of December 2025. The base model remains available despite some deprecated variants. Tests using this model will continue to work.

Comment on lines 12 to 14
# Minimal harmless request (model name may need to be updated)
data = {
"model": "claude-3-opus-20240229", # or another available model
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

current valid model names Anthropic Claude API December 2024 2025

💡 Result:

Here are concise lists of Claude API model names documented by Anthropic for the two dates you asked.

Models available/active around December 2024 (per Anthropic docs in late‑2024):

  • claude-3-haiku-20240307.
  • claude-3-5-haiku-20241022.
    (Several earlier v1/v1.x and claude-instant variants were already retired by late 2024.) [1][2]

Models available/active by December 2025 (current Anthropic model lineup includes):

  • claude-sonnet-4-20250514.
  • claude-opus-4-20250514.
  • claude-opus-4-1-20250805.
  • (newer 4.x releases such as claude-sonnet-4-5 and claude-opus-4-5 are also listed by Anthropic/news sources with API names like claude-sonnet-4-5-20250929 and claude-opus-4-5-20251101). [3][4]

Sources: Anthropic model docs & deprecation pages and recent Anthropic announcements.

  • [1] Anthropic model deprecations / status.
  • [2] Anthropic models overview (models list and API IDs).
  • [3] Anthropic API "List Models" / Get Model pages.
  • [4] Anthropic announcements / press coverage for Sonnet/Opus 4.x (Nov–2025 releases).

If you want, I can fetch the live /v1/models output for your API key to show exactly which model IDs your account can access right now.


Replace hardcoded model name with a currently available Anthropic Claude API model.

The model "claude-3-opus-20240229" is no longer available in the Anthropic API as of December 2025. Current available models include claude-sonnet-4-20250514, claude-opus-4-20250514, and newer releases like claude-opus-4-5-20251101. Update this test to use a valid, currently supported model to avoid API errors when the test runs.

🤖 Prompt for AI Agents
In cortex/utils/api_key_test.py around lines 12 to 14, the test hardcodes the
deprecated model "claude-3-opus-20240229"; replace that string with a currently
supported Anthropic model such as "claude-opus-4-20250514" (or
"claude-sonnet-4-20250514" / "claude-opus-4-5-20251101") so the request
succeeds, or better yet read the model name from a test constant or env var and
default it to "claude-opus-4-20250514".

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cortex/cli.py (1)

388-397: Potential UnboundLocalError for install_id in exception handlers.

install_id is initialized to None at line 389, but this initialization is inside the try block. If an exception occurs before line 389 executes (e.g., at line 391 during _extract_packages_from_commands), the except handlers at lines 543-557 will reference install_id which would raise UnboundLocalError.

Move the install_id = None initialization before the try block to ensure it's always defined.

🔎 Proposed fix
         if not commands:
             self._print_error(
                 "No commands generated with any available provider. Please try again with a different request."
             )
             return 1
 
+        install_id = None
         try:
-            install_id = None
             # Extract packages from commands for tracking
♻️ Duplicate comments (6)
cortex/utils/api_key_test.py (3)

1-3: Remove unused os import.

The os module is imported but never used in this file.


6-25: Update deprecated model and improve exception handling.

The previous review correctly identified:

  1. The model claude-3-opus-20240229 is deprecated
  2. The bare except Exception is overly broad
  3. The docstring is incomplete per coding guidelines

These issues remain unaddressed.


28-42: Improve docstring and exception handling.

The previous review correctly identified:

  1. The bare except Exception is overly broad
  2. The docstring is incomplete per coding guidelines

These issues remain unaddressed.

cortex/first_run_wizard.py (3)

1-10: Module-level environment loading with override=True introduces side effects.

This was flagged in a previous review. The module-level .env loading with override=True conflicts with the centralized env_loader.load_env() approach in cli.py and can cause unexpected behavior during testing and import.


50-63: Enhance docstring with return value details.

Per coding guidelines, this public function should document its return value format.


172-200: Fix Claude API key validation inconsistency.

Line 178 validates that the API key starts with "sk-", but detect_available_providers() (line 55) specifically checks for "sk-ant-" for Anthropic keys. This inconsistency could allow invalid Claude keys to pass validation.

🧹 Nitpick comments (4)
cortex/cli.py (2)

47-61: Enhance docstring per coding guidelines.

Per coding guidelines, public API functions require complete docstrings with Args and Returns sections.

🔎 Proposed improvement
     def _get_api_key_for_provider(self, provider: str) -> str | None:
-        """Get API key for a specific provider."""
+        """Get API key for a specific provider.
+        
+        Args:
+            provider: The provider name ('ollama', 'fake', 'claude', or 'openai').
+            
+        Returns:
+            The API key string if found and valid, None otherwise.
+        """

53-60: Potential prefix overlap between Claude and OpenAI key validation.

The OpenAI check (line 59) uses startswith("sk-") which would also match Anthropic keys that start with "sk-ant-". While the elif structure prevents this from being an issue when provider == "claude" is checked first, consider using a more specific prefix like "sk-proj-" or excluding "sk-ant-" for clarity and future-proofing.

🔎 Proposed fix for clearer validation
         elif provider == "openai":
             key = os.environ.get("OPENAI_API_KEY")
-            if key and key.strip().startswith("sk-"):
+            if key and key.strip().startswith("sk-") and not key.strip().startswith("sk-ant-"):
                 return key.strip()
cortex/first_run_wizard.py (2)

148-170: Add docstring with Args/Returns per coding guidelines.

The _install_suggested_packages method has a brief docstring but is missing Args and Returns sections. Since this method has no parameters (other than self) and no return value, at minimum clarify that it returns None.

🔎 Proposed improvement
     def _install_suggested_packages(self):
-        """Offer to install suggested packages and run the install if user agrees."""
+        """Offer to install suggested packages and run the install if user agrees.
+        
+        Prompts the user to install sample packages (python, numpy, requests)
+        to verify Cortex is working correctly. Executes installations via
+        subprocess if the user agrees.
+        """

354-382: Anthropic key validation correctly uses "sk-ant-" prefix.

The lazy validation loop for Anthropic keys (lines 354-358) correctly checks for the "sk-ant-" prefix, which is consistent with detect_available_providers(). This is good.

However, if the user declines the dry-run test (line 365), the code proceeds silently without any confirmation message. Consider adding a brief acknowledgment like "Skipping dry run test."

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97d27b0 and e0b88fa.

📒 Files selected for processing (4)
  • cortex/cli.py
  • cortex/env_loader.py
  • cortex/first_run_wizard.py
  • cortex/utils/api_key_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • cortex/env_loader.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/utils/api_key_test.py
  • cortex/first_run_wizard.py
  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/first_run_wizard.py (1)
cortex/utils/api_key_test.py (2)
  • test_anthropic_api_key (6-25)
  • test_openai_api_key (28-42)
🪛 GitHub Actions: CI
cortex/first_run_wizard.py

[error] 1-1: test_step_hardware_detection: AttributeError: FirstRunWizard object has no attribute '_step_hardware_detection'.


[error] 1-1: test_detect_hardware_full/test_detect_nvidia_gpu: AttributeError: FirstRunWizard lacks '_detect_hardware' method.

cortex/cli.py

[error] 370-370: test_install_unexpected_error: Unexpected exception during install command execution. Unexpected


[error] 370-370: test_install_value_error: ValueError: Invalid input during install command parsing.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
🔇 Additional comments (2)
cortex/cli.py (1)

772-779: Wizard integration looks correct.

The wizard command now properly instantiates FirstRunWizard with interactive=True and returns the appropriate exit code based on success.

cortex/first_run_wizard.py (1)

416-426: Ollama branch and completion message look good.

The Ollama handling (lines 416-417) and the final success message (lines 423-425) are appropriate. The provider is saved to config and the user is informed of successful setup.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
cortex/cli.py (3)

47-61: Consider enhancing the docstring with parameter and return details.

The docstring is minimal. While this is a private method, adding parameter descriptions and return value details would improve maintainability.

🔎 Suggested docstring enhancement
-    def _get_api_key_for_provider(self, provider: str) -> str | None:
-        """Get API key for a specific provider."""
+    def _get_api_key_for_provider(self, provider: str) -> str | None:
+        """Get API key for a specific provider.
+        
+        Args:
+            provider: The provider name (ollama, fake, claude, openai)
+            
+        Returns:
+            API key string if found and valid, None otherwise
+        """

63-78: Consider adding a user-visible warning for the Ollama fallback.

The fallback to Ollama (line 74) when setup is complete but no valid API key is found only logs in verbose mode. Users running in normal mode won't see any indication that Cortex has switched providers, which could lead to confusion about which provider is being used.

💡 Suggested improvement

Consider adding a user-visible message before the fallback:

         if not wizard.needs_setup():
             # Setup complete, but no valid key - use Ollama as fallback
             self._debug("Setup complete but no valid API key; falling back to Ollama")
+            cx_print("No API key found; using Ollama (local) provider", "info")
             return "ollama-local"

339-386: Multi-provider fallback logic is well-implemented, but exception handling can be simplified.

The provider loop successfully addresses the past review concern by catching Exception (line 378), which includes ValueError and RuntimeError. However, specifying both RuntimeError and Exception is redundant since RuntimeError is a subclass of Exception.

🔎 Simplification suggestion
-            except (RuntimeError, Exception) as e:
+            except Exception as e:
                 self._debug(f"API call failed with {try_provider}: {e}")
                 continue

This catches all exception types (including ValueError, RuntimeError, and any other Exception) as intended, while being more concise.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0b88fa and f399596.

📒 Files selected for processing (2)
  • cortex/cli.py
  • tests/installer/test_parallel_install.py
✅ Files skipped from review due to trivial changes (1)
  • tests/installer/test_parallel_install.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (1)
cortex/first_run_wizard.py (3)
  • FirstRunWizard (147-862)
  • needs_setup (264-266)
  • run (302-426)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (2)
cortex/cli.py (2)

318-318: Good addition of explicit provider control.

The forced_provider parameter enables explicit provider selection for testing and user control, which aligns well with the multi-provider fallback logic introduced in the install flow.


776-779: Excellent fix - wizard now properly continues when API key is present.

This change addresses the core issue from #355. The wizard now runs the complete FirstRunWizard.run() flow, which detects available providers (including those with API keys already in ENV), allows provider confirmation/selection, and continues the setup process rather than exiting prematurely. This matches the expected behavior described in the PR objectives.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_cli_extended.py (1)

86-101: Critical: Tests fail due to missing mock for _get_api_key_for_provider.

The pipeline failures indicate that test_install_dry_run, test_install_no_execute, and test_install_with_execute_success (at lines 100, 122, 153) are failing with "No commands generated with any available provider."

The root cause is that the new multi-provider installation flow in cortex/cli.py (lines 339-387) calls _get_api_key_for_provider(try_provider) directly instead of _get_api_key(). The existing mocks for _get_api_key and _get_provider don't cover this new method, causing the provider loop to find no valid keys and return an error.

🔎 Proposed fix

Add a mock for _get_api_key_for_provider or use environment setup that works with the new logic:

     @patch.object(CortexCLI, "_get_provider", return_value="openai")
     @patch.object(CortexCLI, "_get_api_key", return_value="sk-test-key")
+    @patch.object(CortexCLI, "_get_api_key_for_provider", return_value="sk-test-key")
     @patch.object(CortexCLI, "_animate_spinner", return_value=None)
     @patch.object(CortexCLI, "_clear_line", return_value=None)
     @patch("cortex.cli.CommandInterpreter")
     def test_install_dry_run(
         self,
         mock_interpreter_class,
         _mock_clear_line,
         _mock_spinner,
+        _mock_get_api_key_for_provider,
         _mock_get_api_key,
         _mock_get_provider,
     ) -> None:

Apply similar changes to lines 108-124 and 131-154 for the other failing tests.

Alternatively, use @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-key"}, clear=True) to set up the environment so _get_api_key_for_provider returns a valid key naturally.

♻️ Duplicate comments (2)
cortex/first_run_wizard.py (2)

168-196: Critical: Claude API key validation still inconsistent with provider detection.

Line 174 validates the API key with startswith("sk-"), but detect_available_providers() at line 51 specifically checks for "sk-ant-" for Anthropic/Claude keys. This inconsistency could allow invalid Claude keys (such as OpenAI keys starting with "sk-") to pass validation here, leading to API errors when the key is actually used.

This issue was flagged in a previous review as addressed, but the inconsistency remains.

🔎 Proposed fix
     api_key = self._prompt("Enter your Claude API key: ")
-    if not api_key or not api_key.startswith("sk-"):
+    if not api_key or not api_key.startswith("sk-ant-"):
         print("\n⚠ Invalid API key format")
         return StepResult(success=True, data={"api_provider": "none"})

46-59: Enhance docstring to document return value and detection logic.

Per coding guidelines requiring docstrings for all public APIs, this function's docstring should document:

  • Return type and possible values
  • Detection criteria for each provider
  • Any ordering or priority considerations
🔎 Proposed docstring enhancement
 def detect_available_providers() -> list[str]:
-    """Detect available providers based on API keys and installations."""
+    """Detect available providers based on API keys and installations.
+    
+    Checks for:
+    - Anthropic (Claude) API key: ANTHROPIC_API_KEY environment variable starting with 'sk-ant-'
+    - OpenAI API key: OPENAI_API_KEY environment variable starting with 'sk-'
+    - Ollama: ollama binary available in system PATH
+    
+    Returns:
+        list[str]: List of available provider names. Possible values are 'anthropic', 
+                   'openai', and/or 'ollama'. Returns empty list if no providers detected.
+    """

Based on coding guidelines.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f399596 and cfb9dd5.

📒 Files selected for processing (5)
  • cortex/cli.py
  • cortex/env_loader.py
  • cortex/first_run_wizard.py
  • tests/test_cli.py
  • tests/test_cli_extended.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • cortex/env_loader.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • tests/test_cli.py
  • tests/test_cli_extended.py
  • cortex/cli.py
  • cortex/first_run_wizard.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_cli.py
  • tests/test_cli_extended.py
🧠 Learnings (1)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*install*.py : Dry-run by default for all installations in command execution

Applied to files:

  • tests/test_cli.py
🧬 Code graph analysis (3)
tests/test_cli.py (4)
tests/test_cli_extended.py (1)
  • test_install_dry_run (86-101)
cortex/llm/interpreter.py (1)
  • parse (227-288)
cortex/packages.py (1)
  • parse (383-427)
cortex/cli.py (1)
  • install (312-557)
tests/test_cli_extended.py (2)
tests/test_cli.py (1)
  • test_get_api_key_not_found (32-35)
cortex/cli.py (1)
  • _get_api_key (63-78)
cortex/cli.py (2)
cortex/first_run_wizard.py (2)
  • FirstRunWizard (143-940)
  • run (298-454)
cortex/llm/interpreter.py (2)
  • CommandInterpreter (18-298)
  • parse (227-288)
🪛 GitHub Actions: CI
tests/test_cli_extended.py

[error] 100-100: pytest test_install_dry_run failed: No commands generated with any available provider. Please try again with a different request.


[error] 122-122: pytest test_install_no_execute failed: No commands generated with any available provider. Please try again with a different request.


[error] 153-153: pytest test_install_with_execute_success failed: No commands generated with any available provider. Please try again with a different request.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
🔇 Additional comments (7)
tests/test_cli_extended.py (1)

33-36: LGTM! Test correctly reflects environment-driven provider resolution.

The test now sets CORTEX_PROVIDER="ollama" and expects "ollama-local" as the return value, which aligns with the updated _get_api_key() logic in cortex/cli.py that returns "ollama-local" when the provider is Ollama or when no cloud API keys are available.

cortex/first_run_wizard.py (2)

298-454: Provider detection and setup flow looks solid.

The refactored run() method implements a well-structured onboarding flow:

  • Detects available providers before prompting
  • Auto-selects when only one provider is available
  • Shows menu with status indicators for multiple providers
  • Validates API keys lazily (only when needed)
  • Offers optional dry-run testing to verify setup
  • Handles circular imports correctly with local CortexCLI import

The logic is sound and the user experience improvements are clear.


588-636: LGTM! Hardware detection implementation is clean and robust.

The _detect_hardware() and _step_hardware_detection() methods properly:

  • Import hardware detection functionality from the appropriate module
  • Convert hardware info using asdict() for clean serialization
  • Handle exceptions gracefully with sensible fallback values
  • Display hardware information with conditional formatting based on availability

The code is well-structured and handles edge cases appropriately.

cortex/cli.py (4)

47-61: LGTM! Provider-specific key retrieval is clean and well-structured.

The _get_api_key_for_provider() method properly:

  • Handles all supported providers (ollama, fake, claude, openai)
  • Validates API key prefixes consistently with detect_available_providers() in first_run_wizard.py
  • Returns appropriate default values for local/test providers
  • Returns None when no valid key is found, enabling graceful fallback

The implementation is focused and maintainable.


63-78: LGTM! Updated API key retrieval properly supports multi-provider setup.

The refactored _get_api_key() method:

  • Delegates to the new _get_api_key_for_provider() for cleaner separation
  • Implements appropriate fallback to "ollama-local" for offline mode
  • Provides helpful user guidance when no valid key is found
  • Returns None to signal failure, allowing callers to handle appropriately

This aligns with the PR objective of allowing setup to continue when API keys are present in the environment.


339-387: Multi-provider installation flow is well-designed and resilient.

The enhanced install() method implements robust provider failover:

  • Tries the initial provider first (explicit forced_provider or auto-detected)
  • Adds fallback provider (OpenAI ↔ Claude) when both keys are available
  • Loops through providers until commands are successfully generated
  • Exception handling at line 378 properly catches both RuntimeError and generic Exception
  • Provides clear error message when no provider succeeds

The implementation improves reliability by automatically falling back to alternative providers when API calls fail.

Note: This change requires test updates in test_cli_extended.py (already flagged separately) to mock _get_api_key_for_provider.


776-779: LGTM! Wizard method now executes the actual first-run flow.

The updated wizard() method properly:

  • Instantiates FirstRunWizard with interactive=True for user interaction
  • Executes the actual wizard flow via wizard.run()
  • Returns appropriate exit codes (0 for success, 1 for failure)

This aligns with the PR objective of allowing setup to continue when API keys are already present in the environment.

Comment on lines 67 to +78
@patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-openai-key-123"}, clear=True)
@patch("cortex.cli.CommandInterpreter")
def test_install_dry_run(self, mock_interpreter_class):
mock_interpreter = Mock()
mock_interpreter.parse.return_value = ["apt update", "apt install docker"]
mock_interpreter_class.return_value = mock_interpreter
def test_install_dry_run(self, monkeypatch):
monkeypatch.setenv("OPENAI_API_KEY", "sk-test-openai-key-123")
with patch("cortex.cli.CommandInterpreter") as mock_interpreter_class:
mock_interpreter = Mock()
mock_interpreter.parse.return_value = ["apt update", "apt install docker"]
mock_interpreter_class.return_value = mock_interpreter

result = self.cli.install("docker", dry_run=True)
result = self.cli.install("docker", dry_run=True)

self.assertEqual(result, 0)
mock_interpreter.parse.assert_called_once_with("install docker")
self.assertEqual(result, 0)
mock_interpreter.parse.assert_called_once_with("install docker")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Incompatible mixing of unittest decorators and pytest fixtures.

This test mixes unittest and pytest patterns incorrectly:

  1. Unused decorators: Lines 67-68 have @patch.dict and @patch decorators that are never used in the function body.
  2. Invalid fixture usage: Line 69 accepts a monkeypatch parameter (a pytest fixture), but the class inherits from unittest.TestCase, which doesn't support pytest fixtures.
  3. Redundant environment setup: Line 70 uses monkeypatch.setenv, but line 67's @patch.dict decorator already sets the environment (though it's overridden).

This will cause the test to fail because monkeypatch won't be injected properly.

🔎 Proposed fix - Option 1: Pure unittest style

Remove monkeypatch and use only decorators:

     @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-openai-key-123"}, clear=True)
-    @patch("cortex.cli.CommandInterpreter")
-    def test_install_dry_run(self, monkeypatch):
-        monkeypatch.setenv("OPENAI_API_KEY", "sk-test-openai-key-123")
-        with patch("cortex.cli.CommandInterpreter") as mock_interpreter_class:
-            mock_interpreter = Mock()
-            mock_interpreter.parse.return_value = ["apt update", "apt install docker"]
-            mock_interpreter_class.return_value = mock_interpreter
+    @patch("cortex.cli.CommandInterpreter")
+    def test_install_dry_run(self, mock_interpreter_class):
+        mock_interpreter = Mock()
+        mock_interpreter.parse.return_value = ["apt update", "apt install docker"]
+        mock_interpreter_class.return_value = mock_interpreter

-            result = self.cli.install("docker", dry_run=True)
+        result = self.cli.install("docker", dry_run=True)

-            self.assertEqual(result, 0)
-            mock_interpreter.parse.assert_called_once_with("install docker")
+        self.assertEqual(result, 0)
+        mock_interpreter.parse.assert_called_once_with("install docker")
🔎 Alternative fix - Option 2: Convert to pytest style

If switching to pytest, remove unittest.TestCase inheritance and convert all tests:

-class TestCortexCLI(unittest.TestCase):
-
-    def setUp(self):
-        self.cli = CortexCLI()
+@pytest.fixture
+def cli():
+    return CortexCLI()

     @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-openai-key-123"}, clear=True)
-    def test_install_dry_run(self, monkeypatch):
+    def test_install_dry_run(cli, monkeypatch):
         monkeypatch.setenv("OPENAI_API_KEY", "sk-test-openai-key-123")
         with patch("cortex.cli.CommandInterpreter") as mock_interpreter_class:
             mock_interpreter = Mock()
             mock_interpreter.parse.return_value = ["apt update", "apt install docker"]
             mock_interpreter_class.return_value = mock_interpreter

-            result = self.cli.install("docker", dry_run=True)
+            result = cli.install("docker", dry_run=True)

-            self.assertEqual(result, 0)
+            assert result == 0
             mock_interpreter.parse.assert_called_once_with("install docker")

Note: Option 1 is recommended for consistency with the rest of the test file's unittest style.

🤖 Prompt for AI Agents
In tests/test_cli.py around lines 67 to 79, the test mixes unittest decorators
and a pytest fixture (monkeypatch) causing injection failures; convert it to a
pure unittest-style test by removing the monkeypatch parameter and the inline
monkeypatch.setenv call, keep the @patch.dict(os.environ, {"OPENAI_API_KEY":
"sk-test-openai-key-123"}, clear=True) and
@patch("cortex.cli.CommandInterpreter") decorators, adjust the test signature to
def test_install_dry_run(self, mock_interpreter_class):, create the Mock
interpreter inside the function as currently done and assert calls as before;
alternatively, if you prefer pytest style, remove the unittest.TestCase
inheritance and the decorators and use monkeypatch fixture only, but prefer the
former for consistency.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
cortex/first_run_wizard.py (2)

46-59: Enhance docstring with return value documentation.

Per coding guidelines requiring docstrings for all public APIs, this function should document its return value format and the detection criteria.

🔎 Proposed docstring enhancement
 def detect_available_providers() -> list[str]:
-    """Detect available providers based on API keys and installations."""
+    """Detect available providers based on API keys and installations.
+    
+    Checks for:
+    - Anthropic API key (ANTHROPIC_API_KEY starting with 'sk-ant-')
+    - OpenAI API key (OPENAI_API_KEY starting with 'sk-')
+    - Ollama installation (ollama binary in PATH)
+    
+    Returns:
+        List of available provider names: 'anthropic', 'openai', and/or 'ollama'.
+    """

168-196: Fix Claude API key validation inconsistency.

Line 174 validates that the API key starts with "sk-", but detect_available_providers() (line 51) and the run() method (line 381) specifically check for "sk-ant-" for Anthropic keys. This inconsistency could allow invalid Claude keys to pass validation in _setup_claude_api.

🔎 Proposed fix
     api_key = self._prompt("Enter your Claude API key: ")
-    if not api_key or not api_key.startswith("sk-"):
+    if not api_key or not api_key.startswith("sk-ant-"):
         print("\n⚠ Invalid API key format")
         return StepResult(success=True, data={"api_provider": "none"})
🧹 Nitpick comments (3)
cortex/first_run_wizard.py (3)

1-6: Module-level dotenv loading still present.

This try/except block at module level still executes on import but the load_dotenv function is never called within this block. The imported load_dotenv and Path are unused here. Consider removing this dead code or clarifying its purpose.

🔎 Proposed fix
-try:
-    from pathlib import Path
-
-    from dotenv import load_dotenv
-except ImportError:
-    pass
 """
 First-Run Wizard Module for Cortex Linux

144-166: Add return type hint.

Per coding guidelines, type hints are required. This method should have a return type annotation.

🔎 Proposed fix
-    def _install_suggested_packages(self):
+    def _install_suggested_packages(self) -> None:
         """Offer to install suggested packages and run the install if user agrees."""

544-544: Remove redundant imports.

test_anthropic_api_key and test_openai_api_key are already imported at module level (lines 29). These inline imports at lines 544 and 572 are redundant.

🔎 Proposed fix
                     if do_test.strip().lower() in ("", "y", "yes"):
                         print("\nTesting Claude API key...")
-                        from cortex.utils.api_key_test import test_anthropic_api_key
-
                         if test_anthropic_api_key(existing_claude):
                     if do_test.strip().lower() in ("", "y", "yes"):
                         print("\nTesting OpenAI API key...")
-                        from cortex.utils.api_key_test import test_openai_api_key
-
                         if test_openai_api_key(existing_openai):

Also applies to: 572-572

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfb9dd5 and f95b1e3.

📒 Files selected for processing (3)
  • cortex/first_run_wizard.py
  • tests/test_cli_extended.py
  • tests/test_first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • tests/test_cli_extended.py
  • tests/test_first_run_wizard.py
  • cortex/first_run_wizard.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_cli_extended.py
  • tests/test_first_run_wizard.py
🧬 Code graph analysis (3)
tests/test_cli_extended.py (2)
tests/test_cli.py (1)
  • test_get_api_key_not_found (32-35)
cortex/cli.py (1)
  • _get_api_key (63-78)
tests/test_first_run_wizard.py (3)
cortex/cli.py (1)
  • wizard (772-779)
cortex/llm/interpreter.py (1)
  • parse (227-288)
cortex/packages.py (1)
  • parse (383-427)
cortex/first_run_wizard.py (2)
cortex/utils/api_key_test.py (2)
  • test_anthropic_api_key (6-25)
  • test_openai_api_key (28-42)
cortex/hardware_detection.py (1)
  • detect_hardware (646-648)
🪛 GitHub Actions: CI
tests/test_first_run_wizard.py

[error] 521-521: ruff check . --output-format=github: W293 Blank line contains whitespace in tests/test_first_run_wizard.py:521:1.

🪛 GitHub Check: lint
tests/test_first_run_wizard.py

[failure] 521-521: Ruff (W293)
tests/test_first_run_wizard.py:521:1: W293 Blank line contains whitespace

🪛 GitHub Check: Lint
tests/test_first_run_wizard.py

[failure] 521-521: Ruff (W293)
tests/test_first_run_wizard.py:521:1: W293 Blank line contains whitespace

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
🔇 Additional comments (7)
cortex/first_run_wizard.py (3)

596-608: LGTM!

The _detect_hardware method properly handles exceptions and provides a sensible fallback structure. The inline import avoids circular dependency issues.


610-644: LGTM!

The hardware detection step properly handles the case where GPU list may be empty and provides clear output formatting.


298-318: Provider detection logic addresses the core issue.

The refactored run() method now properly detects available providers via environment variables before prompting, which fixes the reported bug where the wizard exited early despite valid API keys being present. The auto-selection for single providers and menu for multiple providers provides a good UX.

tests/test_first_run_wizard.py (1)

518-530: Test structure looks good.

The integration test properly mocks the CommandInterpreter and verifies the wizard flow completes successfully with correct file creation. The use of sk-ant-test prefix aligns with the validation logic.

tests/test_cli_extended.py (3)

33-36: LGTM!

The test now explicitly sets CORTEX_PROVIDER=ollama to test the Ollama fallback path, which correctly returns "ollama-local". This is clearer than the previous implicit behavior.


81-100: Good test isolation improvements.

The addition of @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-key"}, clear=True) ensures tests are isolated from ambient environment variables. The more specific parse.assert_called_once_with("install docker") assertions improve test clarity and catch regressions more effectively.

Also applies to: 102-121, 123-151


76-79: The mock is ineffective; _get_api_key is never called by install().

The test mocks _get_api_key to return None, but the install() method never calls this method. Instead, it calls _get_api_key_for_provider(), which means the mock has no effect on the code execution. The return code of 0 is correct, but it results from the default behavior of the install() method when execute=False and dry_run=False (both default), not from graceful handling of missing API keys. The test setup should mock _get_api_key_for_provider() instead to actually test missing API key scenarios.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
tests/test_cli_extended.py (1)

76-83: Consider renaming this test to reflect the ollama fallback behavior.

The test name test_install_no_api_key suggests that installation should fail when no API key is present. However, the test now validates that installation succeeds (returns 0) because the system falls back to ollama-local when no API key is configured.

Consider renaming to something like test_install_fallback_to_ollama or test_install_with_ollama_fallback to better convey the expected behavior and avoid confusion for future maintainers.

🔎 Suggested rename
-    def test_install_no_api_key(self, mock_interpreter_class) -> None:
+    def test_install_with_ollama_fallback(self, mock_interpreter_class) -> None:
         mock_interpreter = Mock()
         mock_interpreter.parse.return_value = ["apt update", "apt install docker"]
         mock_interpreter_class.return_value = mock_interpreter
cortex/cli.py (3)

47-61: API key validation is minimal - consider strengthening the checks.

The current validation only checks that API keys start with the expected prefix ("sk-ant-" for Anthropic, "sk-" for OpenAI). Keys that are exactly the prefix (e.g., "sk-ant-" with no additional characters) would pass validation but are invalid.

While invalid keys will ultimately fail when the API is called, adding a minimum length check would catch obvious issues earlier and provide better error messages to users.

🔎 Suggested improvement
     if provider == "claude":
         key = os.environ.get("ANTHROPIC_API_KEY")
-        if key and key.strip().startswith("sk-ant-"):
+        if key and key.strip().startswith("sk-ant-") and len(key.strip()) > 8:
             return key.strip()
     elif provider == "openai":
         key = os.environ.get("OPENAI_API_KEY")
-        if key and key.strip().startswith("sk-"):
+        if key and key.strip().startswith("sk-") and len(key.strip()) > 10:
             return key.strip()

63-78: The fallback logic is correct but could be simplified for clarity.

The condition on lines 70-72 uses a double-negative that makes the logic harder to follow:

if provider == "ollama" or not (
    os.environ.get("OPENAI_API_KEY") or os.environ.get("ANTHROPIC_API_KEY")
):

Consider refactoring for improved readability:

🔎 Suggested simplification
     def _get_api_key(self) -> str | None:
         """Get API key for the current provider."""
         provider = self._get_provider()
         key = self._get_api_key_for_provider(provider)
         if key:
             return key
-        # If provider is ollama or no key is set, always fallback to ollama-local
-        if provider == "ollama" or not (
-            os.environ.get("OPENAI_API_KEY") or os.environ.get("ANTHROPIC_API_KEY")
-        ):
+        # If provider is ollama or no cloud API keys are available, fallback to ollama-local
+        has_cloud_key = bool(os.environ.get("OPENAI_API_KEY") or os.environ.get("ANTHROPIC_API_KEY"))
+        if provider == "ollama" or not has_cloud_key:
             return "ollama-local"
         # Otherwise, prompt user for setup
         self._print_error("No valid API key found.")

376-378: Redundant exception types in the exception handler.

The exception handler on line 376 catches both RuntimeError and Exception:

except (RuntimeError, Exception) as e:

Since Exception is the parent class of RuntimeError (and all other standard exceptions), specifying both is redundant. Catching Exception alone would have the same effect and be more concise.

🔎 Suggested simplification
-            except (RuntimeError, Exception) as e:
+            except Exception as e:
                 self._debug(f"API call failed with {try_provider}: {e}")
                 continue
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f95b1e3 and e245f22.

📒 Files selected for processing (2)
  • cortex/cli.py
  • tests/test_cli_extended.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • tests/test_cli_extended.py
  • cortex/cli.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_cli_extended.py
🧬 Code graph analysis (2)
tests/test_cli_extended.py (2)
tests/test_cli.py (2)
  • test_get_api_key_not_found (32-35)
  • test_install_no_api_key (63-65)
cortex/cli.py (2)
  • _get_api_key (63-78)
  • install (312-555)
cortex/cli.py (1)
cortex/first_run_wizard.py (1)
  • run (298-462)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (4)
tests/test_cli_extended.py (2)

33-36: LGTM! Environment-driven provider testing is correctly implemented.

The test now properly validates that when CORTEX_PROVIDER is set to ollama, the API key resolver returns "ollama-local", which aligns with the updated fallback behavior in cortex/cli.py.


85-85: LGTM! Environment-based API key setup improves test realism.

The addition of @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-key"}, clear=True) to these tests correctly simulates environment-driven provider selection, which aligns with the PR's objective to detect API keys from the environment. The updated assertions verifying specific parse inputs (e.g., "install docker") add precision to the test coverage.

Also applies to: 104-104, 106-106, 125-125, 127-127

cortex/cli.py (2)

339-384: LGTM! Multi-provider fallback improves resilience.

The addition of multi-provider probing logic is a significant improvement. When the initial provider (cloud-based) fails, the system automatically attempts the alternative cloud provider if its API key is available. This increases the likelihood of successful command generation and enhances the user experience.

The forced_provider parameter also enables the wizard's dry-run testing flow as described in the PR objectives.


770-777: LGTM! Wizard integration addresses the PR's core objective.

This change implements the actual first-run wizard flow via FirstRunWizard(interactive=True), which directly addresses issue #355. The wizard will now:

  • Detect API keys already present in the environment
  • Allow the setup to continue (provider confirmation and subsequent steps)
  • Prevent premature exit when a key exists

This is the key fix described in the PR objectives and resolves the bug where the wizard exited early despite a valid API key being exported.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
tests/test_first_run_wizard.py (1)

514-516: Remove redundant decorator.

Line 515 sets ANTHROPIC_API_KEY but line 516 immediately overrides it with clear=True. The first @patch.dict decorator is redundant.

🔎 Proposed fix
     @patch("subprocess.run")
-    @patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-test-12345678"})
     @patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}, clear=True)
     @patch("cortex.cli.CommandInterpreter")
     def test_complete_wizard_flow(self, mock_interpreter_class, mock_run, wizard):
cortex/first_run_wizard.py (2)

174-174: Fix Claude API key validation inconsistency.

Line 174 validates that the API key starts with "sk-", but detect_available_providers() at line 51 specifically checks for "sk-ant-" for Anthropic keys. This inconsistency could allow invalid Claude keys to pass validation.

🔎 Proposed fix
         api_key = self._prompt("Enter your Claude API key: ")
-        if not api_key or not api_key.startswith("sk-"):
+        if not api_key or not api_key.startswith("sk-ant-"):
             print("\n⚠ Invalid API key format")
             return StepResult(success=True, data={"api_provider": "none"})

414-446: Add API validation for Anthropic keys to match OpenAI rigor.

The Anthropic flow (lines 418-423) only validates the key format (sk-ant- prefix) but doesn't test if the key actually works. In contrast, the OpenAI flow at lines 451 and 459 calls test_openai_api_key() to verify the key is valid. This asymmetry could allow users to proceed with invalid Anthropic keys, leading to failures later in the workflow.

🔎 Proposed fix
         if provider == "anthropic":
             key = os.environ.get("ANTHROPIC_API_KEY")
             if key:
                 key = key.strip()
+                if key.startswith("sk-ant-") and test_anthropic_api_key(key):
+                    # Valid existing key, proceed
+                    pass
+                else:
+                    key = None
             while not key or not key.startswith("sk-ant-"):
                 print("\nNo valid Anthropic API key found.")
                 key = self._prompt("Enter your Claude (Anthropic) API key: ")
-                if key and key.startswith("sk-ant-"):
+                if key and key.startswith("sk-ant-") and test_anthropic_api_key(key):
                     self._save_env_var("ANTHROPIC_API_KEY", key)
                     os.environ["ANTHROPIC_API_KEY"] = key
+                else:
+                    print("Invalid API key. Please try again.")
+                    key = None

Based on past review feedback noting validation asymmetry.

🧹 Nitpick comments (1)
cortex/first_run_wizard.py (1)

46-59: Consider enhancing the docstring with return value details.

Per coding guidelines requiring docstrings for all public APIs, this function would benefit from documenting its return value format and detection criteria.

🔎 Suggested docstring enhancement
 def detect_available_providers() -> list[str]:
-    """Detect available providers based on API keys and installations."""
+    """Detect available providers based on API keys and installations.
+    
+    Checks for:
+    - Anthropic API key (ANTHROPIC_API_KEY starting with 'sk-ant-')
+    - OpenAI API key (OPENAI_API_KEY starting with 'sk-')
+    - Ollama installation (ollama binary in PATH)
+    
+    Returns:
+        List of available provider names: 'anthropic', 'openai', and/or 'ollama'.
+    """

Based on coding guidelines.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e245f22 and eba3cd0.

📒 Files selected for processing (2)
  • cortex/first_run_wizard.py
  • tests/test_first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/first_run_wizard.py
  • tests/test_first_run_wizard.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_first_run_wizard.py
🧬 Code graph analysis (2)
cortex/first_run_wizard.py (7)
cortex/utils/api_key_test.py (2)
  • test_anthropic_api_key (6-25)
  • test_openai_api_key (28-42)
cortex/kernel_features/accelerator_limits.py (2)
  • get (66-69)
  • status (94-102)
cortex/env_loader.py (1)
  • load_env (61-113)
cortex/kernel_features/kv_cache_manager.py (1)
  • status (127-134)
cortex/kernel_features/model_lifecycle.py (1)
  • status (135-143)
cortex/hardware_detection.py (1)
  • detect_hardware (646-648)
cortex/logging_system.py (2)
  • info (198-200)
  • warning (202-204)
tests/test_first_run_wizard.py (2)
cortex/cli.py (1)
  • wizard (770-777)
cortex/llm/interpreter.py (1)
  • parse (227-288)
🪛 GitHub Actions: CI
cortex/first_run_wizard.py

[warning] 336-336: Ruff: W293 Blank line contains whitespace.

🪛 GitHub Check: lint
cortex/first_run_wizard.py

[failure] 336-336: Ruff (W293)
cortex/first_run_wizard.py:336:1: W293 Blank line contains whitespace

🪛 GitHub Check: Lint
cortex/first_run_wizard.py

[failure] 336-336: Ruff (W293)
cortex/first_run_wizard.py:336:1: W293 Blank line contains whitespace

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
🔇 Additional comments (5)
tests/test_first_run_wizard.py (1)

522-524: LGTM: Mock setup appropriate for testing.

The CommandInterpreter mock correctly simulates the parse() method returning a command list, which aligns with the integration test requirements for non-interactive wizard flow.

cortex/first_run_wizard.py (4)

144-166: LGTM: Package suggestion helper is well-implemented.

The method appropriately prompts users, handles installation via subprocess with proper error handling, and uses sys.executable to ensure the correct Python interpreter is invoked.


198-226: LGTM: OpenAI setup method includes proper validation.

The method correctly validates both the key format and functionality using test_openai_api_key(), providing a good user experience with immediate feedback and optional package suggestions.


633-645: LGTM: Hardware detection properly delegates to specialized module.

The method cleanly delegates to cortex.hardware_detection.detect_hardware() and provides a safe fallback with unknown values if detection fails, which prevents wizard failure due to hardware detection issues.


1-6: Remove unused import of load_dotenv that is imported but never called.

Lines 1-6 import load_dotenv but this import is unused—the environment variables are loaded via load_env() called at line 303. The unused import should be removed; keeping it only imports a function without executing any module-level side effects.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
cortex/first_run_wizard.py (3)

46-59: Docstring still missing return value documentation.

The previous review requested enhancement of this docstring to document the return value format (list of provider names: 'anthropic', 'openai', 'ollama'). This public API function should comply with the coding guidelines requiring complete docstrings for all public APIs.

Based on coding guidelines.


168-196: CRITICAL: Claude API key validation inconsistency remains unresolved.

Line 174 validates that the API key starts with "sk-", but detect_available_providers() at line 51 specifically checks for "sk-ant-" for Anthropic keys. This inconsistency could allow invalid Claude keys to pass validation.

🔎 Proposed fix
     api_key = self._prompt("Enter your Claude API key: ")
-    if not api_key or not api_key.startswith("sk-"):
+    if not api_key or not api_key.startswith("sk-ant-"):
         print("\n⚠ Invalid API key format")
         return StepResult(success=True, data={"api_provider": "none"})

414-446: Anthropic key validation is less rigorous than OpenAI.

The OpenAI flow (lines 447-464) validates the key using test_openai_api_key() before accepting it (line 451), but the Anthropic flow only checks the prefix format (sk-ant-) without validating the key works. This asymmetry could allow users to proceed with invalid Anthropic keys that have the correct prefix but are expired, revoked, or otherwise non-functional.

🔎 Proposed fix to add API validation for Anthropic
         if provider == "anthropic":
             key = os.environ.get("ANTHROPIC_API_KEY")
             if key:
                 key = key.strip()
+                if key.startswith("sk-ant-") and test_anthropic_api_key(key):
+                    # Valid existing key, proceed
+                    pass
+                else:
+                    key = None
             while not key or not key.startswith("sk-ant-"):
                 print("\nNo valid Anthropic API key found.")
                 key = self._prompt("Enter your Claude (Anthropic) API key: ")
-                if key and key.startswith("sk-ant-"):
+                if key and key.startswith("sk-ant-") and test_anthropic_api_key(key):
                     self._save_env_var("ANTHROPIC_API_KEY", key)
                     os.environ["ANTHROPIC_API_KEY"] = key
+                else:
+                    print("Invalid API key. Please try again.")
+                    key = None
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eba3cd0 and 10f3995.

📒 Files selected for processing (1)
  • cortex/first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/first_run_wizard.py
🧬 Code graph analysis (1)
cortex/first_run_wizard.py (3)
cortex/utils/api_key_test.py (2)
  • test_anthropic_api_key (6-25)
  • test_openai_api_key (28-42)
cortex/env_loader.py (1)
  • load_env (61-113)
cortex/hardware_detection.py (1)
  • detect_hardware (646-648)
🪛 GitHub Actions: CI
cortex/first_run_wizard.py

[error] 1-1: Black formatting check failed. 1 file would be reformatted, 110 files would be left unchanged. Run 'black . --exclude "(venv|.venv|build|dist)"' to fix.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
🔇 Additional comments (4)
cortex/first_run_wizard.py (4)

302-304: Good fix: Using centralized environment loading.

This properly addresses the previous concern about module-level .env loading. The code now uses the centralized load_env() from cortex.env_loader within the run() method, avoiding import-time side effects and ensuring consistent override behavior.


298-499: Excellent refactoring that addresses the core issue.

The refactored run() method successfully addresses issue #355 by:

  • Detecting available providers via detect_available_providers() which checks environment variables
  • Allowing the wizard to continue when API keys are already present in the environment
  • Auto-configuring in non-interactive mode when a provider is available
  • Providing a clear interactive menu showing which providers are available

The logic properly prevents premature wizard exit when valid API keys exist in the environment.


633-645: Well-implemented hardware detection with proper error handling.

This implementation properly delegates to cortex.hardware_detection.detect_hardware and includes appropriate error handling with a safe fallback. The use of asdict() to convert the dataclass result is correct.


28-43: Good additions for enhanced testing and user experience.

The API key test utility imports and DRY_RUN_EXAMPLES constant enhance the wizard's ability to validate API keys and provide varied dry-run test scenarios, improving the first-time user experience.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
cortex/first_run_wizard.py (3)

46-59: Enhance docstring per coding guidelines.

The docstring should document the return value format and detection logic per the requirement that "Docstrings required for all public APIs."

🔎 Proposed docstring enhancement
 def detect_available_providers() -> list[str]:
-    """Detect available providers based on API keys and installations."""
+    """Detect available providers based on API keys and installations.
+    
+    Checks for:
+    - Anthropic: ANTHROPIC_API_KEY starting with 'sk-ant-'
+    - OpenAI: OPENAI_API_KEY starting with 'sk-'
+    - Ollama: ollama binary in PATH
+    
+    Returns:
+        List of available provider names ('anthropic', 'openai', 'ollama').
+    """

Based on coding guidelines.


168-196: Fix Claude API key validation inconsistency.

Line 174 validates that the API key starts with "sk-", but detect_available_providers() (line 51) specifically checks for "sk-ant-" for Anthropic keys. This inconsistency could allow invalid Claude keys to pass validation.

🔎 Proposed fix
     api_key = self._prompt("Enter your Claude API key: ")
-    if not api_key or not api_key.startswith("sk-"):
+    if not api_key or not api_key.startswith("sk-ant-"):
         print("\n⚠ Invalid API key format")
         return StepResult(success=True, data={"api_provider": "none"})

464-496: Add API validation for Anthropic to match OpenAI validation rigor.

The OpenAI flow (lines 501-514) validates the key using test_openai_api_key() before accepting it, but the Anthropic flow (lines 468-473) only checks the prefix format (sk-ant-) without validating the key works. This asymmetry could allow users to proceed with invalid Anthropic keys.

🔎 Proposed fix to add API validation for Anthropic
         if provider == "anthropic":
             key = os.environ.get("ANTHROPIC_API_KEY")
             if key:
                 key = key.strip()
+                if key.startswith("sk-ant-") and test_anthropic_api_key(key):
+                    # Valid existing key, proceed
+                    pass
+                else:
+                    key = None
             while not key or not key.startswith("sk-ant-"):
                 print("\nNo valid Anthropic API key found.")
                 key = self._prompt("Enter your Claude (Anthropic) API key: ")
-                if key and key.startswith("sk-ant-"):
+                if key and key.startswith("sk-ant-") and test_anthropic_api_key(key):
                     self._save_env_var("ANTHROPIC_API_KEY", key)
                     os.environ["ANTHROPIC_API_KEY"] = key
+                else:
+                    print("Invalid API key. Please try again.")
+                    key = None
🧹 Nitpick comments (1)
cortex/first_run_wizard.py (1)

144-167: Add return type hint.

The method has a docstring but is missing a return type hint. Per coding guidelines, type hints are required.

🔎 Proposed fix
-    def _install_suggested_packages(self):
+    def _install_suggested_packages(self) -> None:
         """Offer to install suggested packages and run the install if user agrees."""

Based on coding guidelines.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10f3995 and a866298.

📒 Files selected for processing (1)
  • cortex/first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/first_run_wizard.py
🪛 GitHub Actions: CI
cortex/first_run_wizard.py

[error] 319-319: Ruff: W293 Blank line contains whitespace.

🪛 GitHub Check: lint
cortex/first_run_wizard.py

[failure] 421-421: Ruff (W293)
cortex/first_run_wizard.py:421:1: W293 Blank line contains whitespace


[failure] 417-417: Ruff (W293)
cortex/first_run_wizard.py:417:1: W293 Blank line contains whitespace


[failure] 413-413: Ruff (W293)
cortex/first_run_wizard.py:413:1: W293 Blank line contains whitespace


[failure] 409-409: Ruff (W291)
cortex/first_run_wizard.py:409:36: W291 Trailing whitespace


[failure] 404-404: Ruff (W293)
cortex/first_run_wizard.py:404:1: W293 Blank line contains whitespace


[failure] 359-359: Ruff (W293)
cortex/first_run_wizard.py:359:1: W293 Blank line contains whitespace


[failure] 353-353: Ruff (W291)
cortex/first_run_wizard.py:353:49: W291 Trailing whitespace


[failure] 348-348: Ruff (W293)
cortex/first_run_wizard.py:348:1: W293 Blank line contains whitespace


[failure] 344-344: Ruff (W293)
cortex/first_run_wizard.py:344:1: W293 Blank line contains whitespace


[failure] 319-319: Ruff (W293)
cortex/first_run_wizard.py:319:1: W293 Blank line contains whitespace

🪛 GitHub Check: Lint
cortex/first_run_wizard.py

[failure] 421-421: Ruff (W293)
cortex/first_run_wizard.py:421:1: W293 Blank line contains whitespace


[failure] 417-417: Ruff (W293)
cortex/first_run_wizard.py:417:1: W293 Blank line contains whitespace


[failure] 413-413: Ruff (W293)
cortex/first_run_wizard.py:413:1: W293 Blank line contains whitespace


[failure] 409-409: Ruff (W291)
cortex/first_run_wizard.py:409:36: W291 Trailing whitespace


[failure] 404-404: Ruff (W293)
cortex/first_run_wizard.py:404:1: W293 Blank line contains whitespace


[failure] 359-359: Ruff (W293)
cortex/first_run_wizard.py:359:1: W293 Blank line contains whitespace


[failure] 353-353: Ruff (W291)
cortex/first_run_wizard.py:353:49: W291 Trailing whitespace


[failure] 348-348: Ruff (W293)
cortex/first_run_wizard.py:348:1: W293 Blank line contains whitespace


[failure] 344-344: Ruff (W293)
cortex/first_run_wizard.py:344:1: W293 Blank line contains whitespace


[failure] 319-319: Ruff (W293)
cortex/first_run_wizard.py:319:1: W293 Blank line contains whitespace

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
🔇 Additional comments (1)
cortex/first_run_wizard.py (1)

683-695: Add return type hint (already specified in signature).

The return type is already correctly specified as dict[str, Any] in the signature. Good adherence to coding guidelines.

# Detect available providers (only count valid ones)
available_providers = []
providers = detect_available_providers()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

FIX REQUIRED: Remove trailing whitespace to pass CI.

Multiple lines contain trailing whitespace or blank lines with whitespace, causing the linter to fail. These must be fixed before merge.

Run this command to fix:

#!/bin/bash
# Remove trailing whitespace
sed -i 's/[[:space:]]*$//' cortex/first_run_wizard.py

Also applies to: 344-344, 348-348, 353-353, 359-359, 404-404, 409-409, 413-413, 417-417, 421-421

🧰 Tools
🪛 GitHub Actions: CI

[error] 319-319: Ruff: W293 Blank line contains whitespace.

🪛 GitHub Check: lint

[failure] 319-319: Ruff (W293)
cortex/first_run_wizard.py:319:1: W293 Blank line contains whitespace

🪛 GitHub Check: Lint

[failure] 319-319: Ruff (W293)
cortex/first_run_wizard.py:319:1: W293 Blank line contains whitespace

🤖 Prompt for AI Agents
In cortex/first_run_wizard.py around lines 319, 344, 348, 353, 359, 404, 409,
413, 417 and 421 there are trailing whitespace characters and blank lines with
spaces causing CI lint failures; remove trailing whitespace on those lines (or
run a repository-wide trailing-whitespace removal command) so no line ends with
spaces and save the file, then re-run the linter to confirm the issue is
resolved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
cortex/cli.py (2)

124-191: Consider enhancing the docstring for this public method.

While the basic docstring is present, it could be more comprehensive to document the args parameter structure and the various subcommands (config, enable, disable, dnd, send) this method handles.

📝 Suggested docstring enhancement
 def notify(self, args):
-    """Handle notification commands"""
+    """Handle notification commands.
+    
+    Args:
+        args: Namespace object with notify_action and related subcommand arguments.
+              Supported actions: config, enable, disable, dnd, send
+    
+    Returns:
+        int: 0 on success, 1 on error
+    """

As per coding guidelines, docstrings are required for all public APIs.


372-411: Multi-provider probing logic is sound, but exception handling could be simplified.

The provider fallback logic correctly attempts each provider in sequence and handles failures gracefully. However, the exception handling at line 409 catches both RuntimeError and Exception, which is redundant since Exception is the parent class of RuntimeError.

🔧 Optional simplification
-            except (RuntimeError, Exception) as e:
+            except Exception as e:

Catching Exception alone is sufficient and clearer, as it inherently catches RuntimeError, ValueError, and other exception types.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a866298 and 3dac80a.

📒 Files selected for processing (1)
  • cortex/cli.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/first_run_wizard.py (2)
  • FirstRunWizard (143-1035)
  • run (298-549)
cortex/llm/interpreter.py (2)
  • CommandInterpreter (18-298)
  • parse (227-288)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
🔇 Additional comments (4)
cortex/cli.py (4)

13-13: LGTM: FirstRunWizard import

The import is correctly placed and necessary for the wizard integration at line 808.


48-62: Provider-aware API key resolution looks correct.

The method correctly handles provider-specific key retrieval with appropriate fallbacks for ollama and fake providers. The prefix validation for Anthropic (sk-ant-) and OpenAI (sk-) keys provides basic validation.


64-79: API key fallback logic aligns with PR objectives.

The updated method now detects existing API keys for the current provider and falls back to ollama-local when appropriate. This allows the wizard to continue setup when keys are already present, addressing the core issue in #355.


803-810: Wizard integration correctly addresses the PR objectives.

The wizard method now executes the actual FirstRunWizard flow, which (per the relevant code snippets) will detect available providers, validate existing API keys, and continue the setup flow rather than exiting prematurely. This directly fixes issue #355 where the wizard would exit even when API keys were already exported.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
cortex/first_run_wizard.py (2)

168-198: REGRESSION: Claude API key validation is still inconsistent.

Line 174 validates that the Claude API key starts with "sk-", but Anthropic keys should start with "sk-ant-" (as correctly checked in detect_available_providers() on line 51). This was flagged in a previous review and marked as addressed, but the issue is still present.

This allows invalid Claude keys (including OpenAI keys) to pass validation.

🔎 Proposed fix
     api_key = self._prompt("Enter your Claude API key: ")
-    if not api_key or not api_key.startswith("sk-"):
+    if not api_key or not api_key.startswith("sk-ant-"):
         print("\n⚠ Invalid API key format")
         return StepResult(success=True, data={"api_provider": "none"})

477-508: REGRESSION: Anthropic key validation remains less rigorous than OpenAI.

The Anthropic validation loop (lines 481-485) only checks the prefix format without calling test_anthropic_api_key(), while the OpenAI flow (lines 513, 521) properly validates keys with test_openai_api_key(). This asymmetry was flagged in a previous review and marked as addressed, but is still present.

Users could proceed with syntactically valid but non-working Anthropic keys.

🔎 Proposed fix to add API validation
         if provider == "anthropic":
             key = os.environ.get("ANTHROPIC_API_KEY")
             if key:
                 key = key.strip()
+                if key.startswith("sk-ant-") and test_anthropic_api_key(key):
+                    # Valid existing key, proceed
+                    pass
+                else:
+                    key = None
             while not key or not key.startswith("sk-ant-"):
                 print("\nNo valid Anthropic API key found.")
                 key = self._prompt("Enter your Claude (Anthropic) API key: ")
-                if key and key.startswith("sk-ant-"):
+                if key and key.startswith("sk-ant-") and test_anthropic_api_key(key):
                     os.environ["ANTHROPIC_API_KEY"] = key
+                else:
+                    print("Invalid API key. Please try again.")
+                    key = None
🧹 Nitpick comments (1)
cortex/first_run_wizard.py (1)

329-344: Consider documenting the two-tier validation approach.

The code performs basic validation here (prefix + length checks) and defers full API validation to the provider setup flows. While this approach works, the inconsistency in later API validation (Anthropic lacks full validation) makes the overall flow confusing.

Once the API validation asymmetry is fixed, consider adding a comment here to clarify that this is "quick validation" and full API tests happen during provider setup.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3dac80a and 4af01e5.

📒 Files selected for processing (2)
  • .env.example
  • cortex/first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/first_run_wizard.py
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 5-5: [UnorderedKey] The ANTHROPIC_API_KEY key should go before the OPENAI_API_KEY key

(UnorderedKey)

🪛 GitHub Actions: CI
cortex/first_run_wizard.py

[error] 1-1: black --check . --exclude "(venv|.venv|build|dist)" failed: 1 file would be reformatted. Run 'black --write' to fix formatting.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
🔇 Additional comments (8)
.env.example (1)

1-5: LGTM! Static analysis ordering hint can be safely ignored.

The file structure is clear and provides helpful guidance for users. The ordering of keys (OpenAI before Anthropic) is logical and does not need to be alphabetical.

cortex/first_run_wizard.py (7)

307-317: Good fix: Environment loading is now centralized.

The environment loading has been properly moved into the run() method and uses the centralized load_env() from cortex.env_loader. This resolves the previous concerns about module-level side effects and ensures consistent behavior.


347-385: Excellent UX: Demo mode provides a smooth fallback.

The demo mode flow handles the "no API keys" scenario gracefully:

  • Non-interactive mode auto-enables demo for automation
  • Interactive mode provides clear guidance with links to API key sources
  • Allows users to explore Cortex before committing to API setup

This directly addresses the PR objective of improving the first-time user experience.


411-474: Well-designed interactive provider selection.

The provider menu provides excellent UX:

  • Dynamic options based on available providers
  • Context about current configuration
  • Skip option to preserve existing setup
  • Clear status indicators

The flow naturally guides users through the selection process.


486-508: Smart integration: Dry run testing validates the full stack.

The dry run testing (here and for OpenAI on lines 526-548) provides real validation of the setup by:

  • Testing actual API connectivity
  • Using randomized examples for variety
  • Providing clear success/failure feedback
  • Preventing progression with broken configuration

This is a significant improvement over just API key format validation.


594-601: PR objective achieved: Existing API keys are now detected and used.

This code directly fixes the reported issue where the wizard would exit even with valid API keys in the environment. The wizard now:

  • Detects existing keys from environment variables
  • Shows status indicators in the menu
  • Tests existing keys before prompting for new ones
  • Allows the setup flow to continue

This resolves issue #355.


694-742: Robust hardware detection with proper error handling.

The hardware detection implementation:

  • Delegates to a dedicated module for separation of concerns
  • Provides safe defaults on failure
  • Handles edge cases (missing GPU, multiple GPUs)
  • Presents results clearly to users

144-167: Nice onboarding touch: Package suggestions help validate setup.

The suggested package installation provides immediate value and validates that Cortex is working end-to-end. The implementation properly:

  • Uses subprocess for isolation
  • Captures and displays output
  • Handles errors gracefully

@jaysurse jaysurse force-pushed the fix/cortex-wizard-not-running branch from 4af01e5 to 822098d Compare December 25, 2025 06:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
cortex/first_run_wizard.py (2)

168-198: Fix Claude API key validation to match provider detection logic.

Line 174 validates that the API key starts with "sk-", but detect_available_providers() at line 49-51 specifically checks for "sk-ant-" for Anthropic/Claude keys. This inconsistency allows invalid Claude keys (e.g., OpenAI keys starting with just "sk-") to pass the initial validation.

🔎 Proposed fix
     api_key = self._prompt("Enter your Claude API key: ")
-    if not api_key or not api_key.startswith("sk-"):
+    if not api_key or not api_key.startswith("sk-ant-"):
         print("\n⚠ Invalid API key format")
         return StepResult(success=True, data={"api_provider": "none"})

483-514: Add API validation for Anthropic keys to match OpenAI validation rigor.

The Anthropic key validation only checks the prefix format (sk-ant-) without validating that the key actually works. The OpenAI flow (lines 519-528) validates keys using test_openai_api_key() before accepting them. This asymmetry could allow users to proceed with invalid Anthropic keys.

🔎 Proposed fix to add API validation
         if provider == "anthropic":
             key = os.environ.get("ANTHROPIC_API_KEY")
             if key:
                 key = key.strip()
+                if key.startswith("sk-ant-") and test_anthropic_api_key(key):
+                    # Valid existing key, proceed
+                    pass
+                else:
+                    key = None
             while not key or not key.startswith("sk-ant-"):
                 print("\nNo valid Anthropic API key found.")
                 key = self._prompt("Enter your Claude (Anthropic) API key: ")
-                if key and key.startswith("sk-ant-"):
+                if key and key.startswith("sk-ant-") and test_anthropic_api_key(key):
                     os.environ["ANTHROPIC_API_KEY"] = key
+                else:
+                    print("Invalid API key. Please try again.")
+                    key = None
🧹 Nitpick comments (3)
cortex/first_run_wizard.py (3)

46-59: Enhance docstring to document return value and detection logic.

Per coding guidelines requiring docstrings for all public APIs, this function should document its return value format and detection criteria.

🔎 Proposed docstring enhancement
 def detect_available_providers() -> list[str]:
-    """Detect available providers based on API keys and installations."""
+    """Detect available providers based on API keys and installations.
+    
+    Checks for:
+    - Anthropic: ANTHROPIC_API_KEY environment variable starting with 'sk-ant-'
+    - OpenAI: OPENAI_API_KEY environment variable starting with 'sk-'
+    - Ollama: ollama binary present in PATH
+    
+    Returns:
+        List of available provider names ('anthropic', 'openai', 'ollama').
+        Empty list if no providers are detected.
+    """
     providers = []

Based on coding guidelines.


331-346: Consider API validation for provider detection to prevent false positives.

The provider validation only performs format checks (prefix and length), not actual API validation. This could result in invalid keys being counted as available providers, leading to confusing UX when users select a provider that subsequently fails.

Compare with lines 519-523 where OpenAI keys are validated using test_openai_api_key(). Consider applying similar validation here for more accurate provider detection.

🔎 Proposed enhancement
         # Validate providers - only count ones with working API keys
         for provider in providers:
             if provider == "ollama":
                 available_providers.append(provider)  # Ollama doesn't need validation
             elif provider == "openai":
                 key = os.environ.get("OPENAI_API_KEY", "")
-                if key.startswith("sk-") and len(key) > 20:  # Basic validation
+                if key.startswith("sk-") and test_openai_api_key(key):
                     available_providers.append(provider)
             elif provider == "anthropic":
                 key = os.environ.get("ANTHROPIC_API_KEY", "")
-                if key.startswith("sk-ant-") and len(key) > 20:  # Basic validation
+                if key.startswith("sk-ant-") and test_anthropic_api_key(key):
                     available_providers.append(provider)

714-748: Consider documenting single GPU limitation.

Line 733 takes only the first GPU from the list. For systems with multiple GPUs, this might be unexpected behavior. Consider either displaying all GPUs or documenting this limitation.

🔎 Possible enhancement to show all GPUs
         print("  • CPU:", cpu.get("model", "Unknown"))
         if gpu_list:
-            gpu = gpu_list[0]  # Take first GPU
-            gpu_vendor = gpu.get("vendor", "unknown")
-            gpu_model = gpu.get("model", "Detected")
-            if gpu_vendor != "unknown":
-                print(f"  • GPU: {gpu_model} ({gpu_vendor})")
-            else:
-                print("  • GPU: Detected")
+            for idx, gpu in enumerate(gpu_list):
+                gpu_vendor = gpu.get("vendor", "unknown")
+                gpu_model = gpu.get("model", "Detected")
+                prefix = "  • GPU" if len(gpu_list) == 1 else f"  • GPU {idx + 1}"
+                if gpu_vendor != "unknown":
+                    print(f"{prefix}: {gpu_model} ({gpu_vendor})")
+                else:
+                    print(f"{prefix}: Detected")
         else:
             print("  • GPU: Not detected")
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4af01e5 and 822098d.

📒 Files selected for processing (2)
  • .env.example
  • cortex/first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/first_run_wizard.py
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 5-5: [UnorderedKey] The ANTHROPIC_API_KEY key should go before the OPENAI_API_KEY key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Package
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
🔇 Additional comments (3)
cortex/first_run_wizard.py (2)

306-319: Good approach: centralized environment loading at runtime.

The environment loading is now properly centralized within the run() method, avoiding import-time side effects. Creating .env from .env.example when missing is helpful for first-time users.


515-531: Good practice: validating API keys before accepting them.

The OpenAI flow properly validates keys using test_openai_api_key() before accepting them, providing better UX by catching invalid keys early. This approach should be consistently applied to all providers.

.env.example (1)

1-5: LGTM! Example file is clear and functional.

The .env.example file provides clear placeholders for API keys with helpful comments. The static analysis warning about key ordering is pedantic and can be safely ignored for an example file—the order doesn't affect functionality.

@jaysurse jaysurse force-pushed the fix/cortex-wizard-not-running branch from 822098d to 4be3bb2 Compare December 25, 2025 06:45
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
cortex/first_run_wizard.py (3)

46-59: Enhance docstring with return value documentation.

Per coding guidelines requiring docstrings for all public APIs, this function should document what it returns and the criteria for each provider.

🔎 Proposed docstring enhancement
 def detect_available_providers() -> list[str]:
-    """Detect available providers based on API keys and installations."""
+    """Detect available providers based on API keys and installations.
+
+    Checks for:
+    - Anthropic API key (ANTHROPIC_API_KEY starting with 'sk-ant-')
+    - OpenAI API key (OPENAI_API_KEY starting with 'sk-')
+    - Ollama installation (ollama binary in PATH)
+
+    Returns:
+        List of available provider names: 'anthropic', 'openai', and/or 'ollama'.
+    """
     providers = []

483-514: Anthropic key validation is less rigorous than OpenAI.

The OpenAI flow (lines 516-531) validates the key using test_openai_api_key() before accepting it, but the Anthropic flow (lines 483-514) only checks the prefix format (sk-ant-) without actually validating the key works via test_anthropic_api_key(). This asymmetry could allow users to proceed with invalid Anthropic keys.

🔎 Proposed fix to add API validation for Anthropic
         if provider == "anthropic":
             key = os.environ.get("ANTHROPIC_API_KEY")
             if key:
                 key = key.strip()
-            while not key or not key.startswith("sk-ant-"):
+                if key.startswith("sk-ant-") and test_anthropic_api_key(key):
+                    pass  # Valid existing key
+                else:
+                    key = None
+            while not key or not key.startswith("sk-ant-"):
                 print("\nNo valid Anthropic API key found.")
                 key = self._prompt("Enter your Claude (Anthropic) API key: ")
-                if key and key.startswith("sk-ant-"):
+                if key and key.startswith("sk-ant-") and test_anthropic_api_key(key):
                     os.environ["ANTHROPIC_API_KEY"] = key
+                else:
+                    print("Invalid API key. Please try again.")
+                    key = None

168-198: Claude API key validation is inconsistent with provider detection.

Line 174 validates that the API key starts with "sk-", but detect_available_providers() (line 51) specifically checks for "sk-ant-" for Anthropic keys. This inconsistency could allow invalid Claude keys to pass validation in _setup_claude_api() while failing detection elsewhere.

🔎 Proposed fix
     api_key = self._prompt("Enter your Claude API key: ")
-    if not api_key or not api_key.startswith("sk-"):
+    if not api_key or not api_key.startswith("sk-ant-"):
         print("\n⚠ Invalid API key format")
         return StepResult(success=True, data={"api_provider": "none"})
🧹 Nitpick comments (3)
cortex/first_run_wizard.py (3)

1-6: Module-level import side effects removed but docstring placement is unusual.

The module-level load_dotenv call from previous commits has been removed (addressed per past review). However, the try/except block at lines 1-6 imports Path and load_dotenv but doesn't use them before the module docstring at line 7. The Path import is redundant since it's imported again at line 25.

🔎 Proposed fix - remove redundant try/except block
-try:
-    from pathlib import Path
-
-    from dotenv import load_dotenv
-except ImportError:
-    pass
 """
 First-Run Wizard Module for Cortex Linux

144-166: Unused environment copy and missing return type hint.

  1. Line 152: env = os.environ.copy() is assigned but the copy is not modified before passing to subprocess.run(). You can pass os.environ directly or remove the variable.

  2. Per coding guidelines, type hints are required. This method should have a return type annotation.

🔎 Proposed fix
-    def _install_suggested_packages(self):
+    def _install_suggested_packages(self) -> None:
         """Offer to install suggested packages and run the install if user agrees."""
         suggestions = ["python", "numpy", "requests"]
         print("\nTry installing a package to verify Cortex is ready:")
         for pkg in suggestions:
             print(f"  cortex install {pkg}")
         resp = self._prompt("Would you like to install these packages now? [Y/n]: ", default="y")
         if resp.strip().lower() in ("", "y", "yes"):
-            env = os.environ.copy()
             for pkg in suggestions:
                 print(f"\nInstalling {pkg}...")
                 try:
                     result = subprocess.run(
                         [sys.executable, "-m", "cortex.cli", "install", pkg],
                         capture_output=True,
                         text=True,
-                        env=env,
                     )

648-648: Remove redundant imports.

test_anthropic_api_key and test_openai_api_key are already imported at line 29. The local imports at lines 648 and 676 are unnecessary.

🔎 Proposed fix
                     if do_test.strip().lower() in ("", "y", "yes"):
                         print("\nTesting Claude API key...")
-                        from cortex.utils.api_key_test import test_anthropic_api_key
-
                         if test_anthropic_api_key(existing_claude):
                     if do_test.strip().lower() in ("", "y", "yes"):
                         print("\nTesting OpenAI API key...")
-                        from cortex.utils.api_key_test import test_openai_api_key
-
                         if test_openai_api_key(existing_openai):

Also applies to: 676-676

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 822098d and 4be3bb2.

📒 Files selected for processing (2)
  • .env.example
  • cortex/first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/first_run_wizard.py
🧬 Code graph analysis (1)
cortex/first_run_wizard.py (3)
cortex/utils/api_key_test.py (2)
  • test_anthropic_api_key (6-25)
  • test_openai_api_key (28-42)
cortex/env_loader.py (1)
  • load_env (61-113)
cortex/hardware_detection.py (1)
  • detect_hardware (656-658)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 5-5: [UnorderedKey] The ANTHROPIC_API_KEY key should go before the OPENAI_API_KEY key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
🔇 Additional comments (7)
cortex/first_run_wizard.py (6)

200-230: LGTM!

The OpenAI API setup method correctly validates the key prefix and tests the key when in interactive mode.


232-235: LGTM, but consider adding return type hint.

The Ollama setup is straightforward. Per coding guidelines, type hints are required, which this method already has in its signature via the -> StepResult annotation.


499-514: LGTM!

The dry-run test flow with lazy CLI import to avoid circular dependencies is well-structured. Error handling appropriately returns False on failures.


700-712: LGTM!

The hardware detection delegation is well-implemented with appropriate error handling and fallback values.


714-748: LGTM!

The hardware detection step properly handles the GPU list structure and provides clear, user-friendly output.


302-319: Core fix for PR objective: Environment loading before provider detection.

This change properly addresses the issue from #355. By calling load_env() before detect_available_providers(), API keys from .env files are now available when the wizard runs, preventing premature exit when valid keys exist.

.env.example (1)

1-5: LGTM!

The example file correctly demonstrates the expected API key formats with appropriate placeholders. The static analysis hint about alphabetical key ordering is a minor stylistic preference that can be ignored.

@jaysurse jaysurse force-pushed the fix/cortex-wizard-not-running branch from 4be3bb2 to 3552863 Compare December 25, 2025 06:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
cortex/first_run_wizard.py (2)

204-215: Enhance docstring with return value details.

Per coding guidelines requiring docstrings for all public APIs, this function should document its return value format.

🔎 Proposed docstring enhancement
 def detect_available_providers() -> list[str]:
-    """Detect available providers based on valid (non-blank) API keys in .env file."""
+    """Detect available providers based on valid (non-blank) API keys in .env file.
+    
+    Checks for:
+    - Anthropic API key (ANTHROPIC_API_KEY starting with 'sk-ant-')
+    - OpenAI API key (OPENAI_API_KEY starting with 'sk-')
+    - Ollama installation (ollama binary in PATH)
+    
+    Returns:
+        List of available provider names: 'anthropic', 'openai', and/or 'ollama'.
+    """
     providers = []

Based on coding guidelines.


1-8: Module-level dotenv loading still has issues flagged in past reviews.

This pattern was previously flagged for causing import-time side effects and using override=True which contradicts the centralized loader's design. Additionally, the imports within the try block trigger an I001 linter failure.

If you must keep this pattern, at minimum fix the import sorting to pass CI. However, consider removing this block entirely and relying on the centralized load_env() approach as previously suggested.

🔎 Proposed fix to address linter error
 try:
-    from pathlib import Path
     from dotenv import load_dotenv
+    from pathlib import Path
     # Load from parent directory .env as well
     load_dotenv(dotenv_path=Path.cwd().parent / ".env", override=True)
     load_dotenv(dotenv_path=Path.cwd() / ".env", override=True)
 except ImportError:
     pass
🧹 Nitpick comments (3)
.env.example (1)

1-5: LGTM!

Clear and helpful example file for users to configure their API keys. The placeholder values appropriately indicate the expected key format for each provider.

Nit: Consider alphabetically ordering keys (ANTHROPIC_API_KEY before OPENAI_API_KEY) to satisfy dotenv-linter's UnorderedKey rule, though this is purely a style preference for example files.

cortex/first_run_wizard.py (2)

534-547: Consider extracting duplicate dry-run verification logic.

The dry-run verification code for Anthropic (lines 534-547) and OpenAI (lines 572-585) is nearly identical. Consider extracting this into a helper method to reduce duplication.

🔎 Proposed helper method
def _verify_provider_setup(self, provider: str) -> bool:
    """Run a dry-run verification for the given provider."""
    random_example = random.choice(DRY_RUN_EXAMPLES)
    print(f'\nVerifying setup with dry run: cortex install "{random_example}"...')
    try:
        from cortex.cli import CortexCLI
        cli = CortexCLI()
        result = cli.install(random_example, execute=False, dry_run=True, forced_provider=provider)
        if result != 0:
            print("\n❌ Dry run failed. Please check your API key and network.")
            return False
        print("\n✅ API key verified successfully!")
        return True
    except Exception as e:
        print(f"\n❌ Error during verification: {e}")
        return False

Then call it as:

if not self._verify_provider_setup("claude"):
    return False

393-412: Add return type hint.

Missing return type annotation. Per coding guidelines, type hints are required.

-    def _install_suggested_packages(self):
+    def _install_suggested_packages(self) -> None:
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4be3bb2 and b2460d7.

📒 Files selected for processing (2)
  • .env.example
  • cortex/first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/first_run_wizard.py
🧬 Code graph analysis (1)
cortex/first_run_wizard.py (2)
cortex/utils/api_key_test.py (2)
  • test_anthropic_api_key (6-25)
  • test_openai_api_key (28-42)
cortex/cli.py (1)
  • _print_error (107-108)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 5-5: [UnorderedKey] The ANTHROPIC_API_KEY key should go before the OPENAI_API_KEY key

(UnorderedKey)

🪛 GitHub Actions: CI
cortex/first_run_wizard.py

[error] 2-2: Ruff check failed (I001): Import block is unsorted or un-formatted. Run 'ruff format' or fix imports and re-run.

🪛 GitHub Check: lint
cortex/first_run_wizard.py

[failure] 96-96: Ruff (W293)
cortex/first_run_wizard.py:96:1: W293 Blank line contains whitespace


[failure] 90-90: Ruff (W293)
cortex/first_run_wizard.py:90:1: W293 Blank line contains whitespace


[failure] 84-84: Ruff (W293)
cortex/first_run_wizard.py:84:1: W293 Blank line contains whitespace


[failure] 78-78: Ruff (UP015)
cortex/first_run_wizard.py:78:29: UP015 Unnecessary mode argument


[failure] 76-76: Ruff (W293)
cortex/first_run_wizard.py:76:1: W293 Blank line contains whitespace


[failure] 73-73: Ruff (W293)
cortex/first_run_wizard.py:73:1: W293 Blank line contains whitespace


[failure] 62-62: Ruff (W293)
cortex/first_run_wizard.py:62:1: W293 Blank line contains whitespace


[failure] 58-58: Ruff (W293)
cortex/first_run_wizard.py:58:1: W293 Blank line contains whitespace


[failure] 17-32: Ruff (I001)
cortex/first_run_wizard.py:17:1: I001 Import block is un-sorted or un-formatted


[failure] 2-3: Ruff (I001)
cortex/first_run_wizard.py:2:5: I001 Import block is un-sorted or un-formatted

🪛 GitHub Check: Lint
cortex/first_run_wizard.py

[failure] 96-96: Ruff (W293)
cortex/first_run_wizard.py:96:1: W293 Blank line contains whitespace


[failure] 90-90: Ruff (W293)
cortex/first_run_wizard.py:90:1: W293 Blank line contains whitespace


[failure] 84-84: Ruff (W293)
cortex/first_run_wizard.py:84:1: W293 Blank line contains whitespace


[failure] 78-78: Ruff (UP015)
cortex/first_run_wizard.py:78:29: UP015 Unnecessary mode argument


[failure] 76-76: Ruff (W293)
cortex/first_run_wizard.py:76:1: W293 Blank line contains whitespace


[failure] 73-73: Ruff (W293)
cortex/first_run_wizard.py:73:1: W293 Blank line contains whitespace


[failure] 62-62: Ruff (W293)
cortex/first_run_wizard.py:62:1: W293 Blank line contains whitespace


[failure] 58-58: Ruff (W293)
cortex/first_run_wizard.py:58:1: W293 Blank line contains whitespace


[failure] 17-32: Ruff (I001)
cortex/first_run_wizard.py:17:1: I001 Import block is un-sorted or un-formatted


[failure] 2-3: Ruff (I001)
cortex/first_run_wizard.py:2:5: I001 Import block is un-sorted or un-formatted

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
🔇 Additional comments (1)
cortex/first_run_wizard.py (1)

414-603: Core wizard flow correctly addresses the PR objective.

The run() method now properly:

  1. Reloads the .env file to get fresh values (lines 427-434)
  2. Detects available providers from .env (line 445)
  3. Shows provider selection menu with status indicators (lines 463-491)
  4. Handles existing valid keys without prompting (lines 513, 528-529 for Anthropic; 551, 566-567 for OpenAI)
  5. Only prompts for key entry when the key is missing or blank

This correctly fixes issue #355 where the wizard exited early despite a valid API key being present.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
cortex/utils/api_key_validator.py (1)

1-1: Remove unused os import.

The os module is imported but never used in this file. This also resolves the I001 import formatting issue mentioned in past reviews.

🔎 Proposed fix
-import os
-
 import requests
🧹 Nitpick comments (3)
cortex/utils/api_key_validator.py (1)

6-24: Consider logging validation failures for debugging.

The broad exception handling silently returns False without logging. While acceptable for a simple validator, adding optional debug logging could help troubleshoot API connectivity issues during setup.

Example enhancement
def validate_anthropic_api_key(api_key: str) -> bool:
    """Validate Anthropic (Claude) API key by making a minimal request."""
    try:
        headers = {
            "x-api-key": api_key,
            "anthropic-version": "2023-06-01",
            "content-type": "application/json",
        }
        data = {
            "model": "claude-3-opus-20240229",
            "max_tokens": 1,
            "messages": [{"role": "user", "content": "Hello"}],
        }
        resp = requests.post(
            "https://api.anthropic.com/v1/messages", headers=headers, json=data, timeout=10
        )
        return resp.status_code == 200
    except Exception as e:
        logger.debug(f"Anthropic API key validation failed: {e}")
        return False
cortex/first_run_wizard.py (2)

42-87: Consider adding error handling for malformed .env files.

The read_key_from_env_file function handles basic parsing but could be more robust when encountering malformed entries (e.g., multiple = signs, unclosed quotes).

Example enhancement

For line 72, consider using line.partition("=") more carefully or adding a try-except around the quote stripping to handle edge cases like unbalanced quotes.


168-179: Enhance docstring with return value details.

Per coding guidelines requiring docstrings for all public APIs, this function should document its return value format and detection criteria.

🔎 Proposed docstring enhancement
 def detect_available_providers() -> list[str]:
-    """Detect available providers based on valid (non-blank) API keys in .env file."""
+    """Detect available providers based on valid (non-blank) API keys in .env file.
+    
+    Checks for:
+    - Anthropic API key (ANTHROPIC_API_KEY starting with 'sk-ant-')
+    - OpenAI API key (OPENAI_API_KEY starting with 'sk-')
+    - Ollama installation (ollama binary in PATH)
+    
+    Returns:
+        List of available provider names: 'anthropic', 'openai', and/or 'ollama'.
+    """

Based on coding guidelines.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2460d7 and fc53dd4.

📒 Files selected for processing (3)
  • cortex/first_run_wizard.py
  • cortex/utils/api_key_validator.py
  • tests/test_first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/utils/api_key_validator.py
  • tests/test_first_run_wizard.py
  • cortex/first_run_wizard.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_first_run_wizard.py
🧬 Code graph analysis (2)
tests/test_first_run_wizard.py (1)
cortex/first_run_wizard.py (1)
  • _step_api_setup (640-653)
cortex/first_run_wizard.py (1)
cortex/utils/api_key_validator.py (2)
  • validate_anthropic_api_key (6-24)
  • validate_openai_api_key (27-41)
🪛 GitHub Actions: CI
cortex/first_run_wizard.py

[error] 772-772: Command 'ruff check . --output-format=github' failed. Ruff error: F822 Undefined name 'test_anthropic_api_key' in all.

🪛 GitHub Check: lint
cortex/utils/api_key_validator.py

[failure] 46-46: Ruff (W292)
cortex/utils/api_key_validator.py:46:46: W292 No newline at end of file

tests/test_first_run_wizard.py

[failure] 545-545: Ruff (W292)
tests/test_first_run_wizard.py:545:34: W292 No newline at end of file

cortex/first_run_wizard.py

[failure] 781-781: Ruff (W292)
cortex/first_run_wizard.py:781:67: W292 No newline at end of file


[failure] 773-773: Ruff (F822)
cortex/first_run_wizard.py:773:5: F822 Undefined name test_openai_api_key in __all__


[failure] 772-772: Ruff (F822)
cortex/first_run_wizard.py:772:5: F822 Undefined name test_anthropic_api_key in __all__

🪛 GitHub Check: Lint
cortex/utils/api_key_validator.py

[failure] 46-46: Ruff (W292)
cortex/utils/api_key_validator.py:46:46: W292 No newline at end of file

tests/test_first_run_wizard.py

[failure] 545-545: Ruff (W292)
tests/test_first_run_wizard.py:545:34: W292 No newline at end of file

cortex/first_run_wizard.py

[failure] 781-781: Ruff (W292)
cortex/first_run_wizard.py:781:67: W292 No newline at end of file


[failure] 773-773: Ruff (F822)
cortex/first_run_wizard.py:773:5: F822 Undefined name test_openai_api_key in __all__


[failure] 772-772: Ruff (F822)
cortex/first_run_wizard.py:772:5: F822 Undefined name test_anthropic_api_key in __all__

🪛 Gitleaks (8.30.0)
tests/test_first_run_wizard.py

[high] 269-269: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
🔇 Additional comments (6)
tests/test_first_run_wizard.py (1)

261-276: LGTM! Improved test coverage for provider-specific API key detection.

The refactored tests now explicitly verify both Anthropic and OpenAI key handling, with proper environment isolation using clear=True. This aligns well with the wizard's environment-based provider discovery.

cortex/first_run_wizard.py (5)

21-22: Imports look good.

The import of validation utilities from cortex.utils.api_key_validator is correct. However, note that the __all__ export list at lines 772-773 references undefined names (see separate comment).


318-351: Good validation logic with clear user guidance.

The _prompt_for_api_key method properly validates key format and rejects blank inputs, addressing the core issue from #355. The user guidance for obtaining keys is helpful.


378-577: Well-structured provider selection flow.

The refactored run() method implements the fix described in the PR objectives:

  • Detects existing API keys in environment
  • Prevents premature wizard exit when keys are present
  • Allows continuation of setup flow with provider confirmation

The dry-run verification at lines 498-511 and 548-561 is a nice touch for validating the setup.


498-498: Verify noqa comment justification.

The # noqa: S311 comment suppresses the bandit security warning about using random.choice instead of secrets.choice. Since this is only selecting an example string for display (not for cryptographic purposes), the suppression is appropriate.


640-653: Legacy test compatibility maintained correctly.

The _step_api_setup method properly maintains backward compatibility with existing tests while the main run() method implements the new provider selection flow. This is the method that tests in test_first_run_wizard.py are exercising.


# Aliases for backward compatibility
test_anthropic_api_key = validate_anthropic_api_key
test_openai_api_key = validate_openai_api_key No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix missing newline at end of file to pass CI.

The linter is failing because the file does not end with a newline character (W292).

🔎 Proposed fix
 # Aliases for backward compatibility
 test_anthropic_api_key = validate_anthropic_api_key
 test_openai_api_key = validate_openai_api_key
+
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test_openai_api_key = validate_openai_api_key
test_openai_api_key = validate_openai_api_key
🧰 Tools
🪛 GitHub Check: lint

[failure] 46-46: Ruff (W292)
cortex/utils/api_key_validator.py:46:46: W292 No newline at end of file

🪛 GitHub Check: Lint

[failure] 46-46: Ruff (W292)
cortex/utils/api_key_validator.py:46:46: W292 No newline at end of file

🤖 Prompt for AI Agents
In cortex/utils/api_key_validator.py around line 46, the file is missing a
trailing newline which causes linter CI error W292; fix it by adding a single
newline character at the end of the file so the last line ends with '\n' and
save the file.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b12c7b and 8a92bf9.

📒 Files selected for processing (1)
  • cortex/first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/first_run_wizard.py
🧬 Code graph analysis (1)
cortex/first_run_wizard.py (1)
cortex/utils/api_key_validator.py (2)
  • validate_anthropic_api_key (10-28)
  • validate_openai_api_key (34-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)

Comment on lines 21 to 24
from cortex.utils.api_key_validator import (
validate_anthropic_api_key,
validate_openai_api_key,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused imports or implement actual API key validation.

The imported validate_anthropic_api_key and validate_openai_api_key functions are never called in this file. The is_valid_api_key() function (lines 104-111) only checks key prefixes without validating that the keys actually work. Consider either:

  1. Removing these unused imports, or
  2. Using these validators in get_valid_api_key() to ensure keys are not only well-formed but also functional before continuing the wizard.
🔎 Option 1: Remove unused imports
-from cortex.utils.api_key_validator import (
-    validate_anthropic_api_key,
-    validate_openai_api_key,
-)
🔎 Option 2: Use validators in get_valid_api_key()
 def get_valid_api_key(env_var: str, key_type: str) -> str | None:
     key = read_key_from_env_file(env_var)
-    if key and is_valid_api_key(key, key_type):
+    if key and is_valid_api_key(key, key_type):
+        # Validate the key actually works
+        if key_type == "anthropic" and not validate_anthropic_api_key(key):
+            return None
+        if key_type == "openai" and not validate_openai_api_key(key):
+            return None
         os.environ[env_var] = key
         return key
     os.environ.pop(env_var, None)
     return None
🤖 Prompt for AI Agents
In cortex/first_run_wizard.py around lines 21-24, the imports
validate_anthropic_api_key and validate_openai_api_key are unused; either remove
them or wire them into get_valid_api_key() so API keys are actually tested. If
choosing to validate, update get_valid_api_key() to call the appropriate
validator for the detected provider (awaiting if the validators are async),
handle exceptions and false returns (log or surface an error to the wizard), and
only accept/return the key when the validator confirms it works; otherwise
prompt the user again. If you prefer not to validate here, remove the two
imports and any related dead code.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/test_first_run_wizard.py (1)

277-287: Consider removing redundant environment cleanup.

Lines 281-282 explicitly pop ANTHROPIC_API_KEY and OPENAI_API_KEY after line 277 already clears the entire environment with clear=True. The explicit pop() calls are no-ops and can be safely removed.

🔎 Proposed simplification
     @patch.dict(os.environ, {}, clear=True)
     def test_step_api_setup_no_key(self, wizard):
         """Test API setup without existing key."""
-        # Remove any existing keys
-        os.environ.pop("ANTHROPIC_API_KEY", None)
-        os.environ.pop("OPENAI_API_KEY", None)
-
         result = wizard._step_api_setup()
 
         assert result.success is True
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a92bf9 and de7f775.

📒 Files selected for processing (1)
  • tests/test_first_run_wizard.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • tests/test_first_run_wizard.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_first_run_wizard.py
🧬 Code graph analysis (1)
tests/test_first_run_wizard.py (1)
cortex/cli.py (1)
  • wizard (803-810)
🪛 Gitleaks (8.30.0)
tests/test_first_run_wizard.py

[high] 269-269: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
🔇 Additional comments (2)
tests/test_first_run_wizard.py (2)

261-267: LGTM! Test correctly validates Anthropic key detection.

The renamed test clearly validates that the wizard detects an existing ANTHROPIC_API_KEY and sets the provider accordingly. This aligns with the PR objective of allowing the wizard to continue when API keys are already present in the environment.


269-275: LGTM! Test correctly validates OpenAI key detection.

The new test appropriately validates that the wizard detects an existing OPENAI_API_KEY and sets the provider to "openai". The use of clear=True ensures proper test isolation.

Note: The static analysis tool flagged the mock API key as sensitive, but this is a false positive—it's test data, not a real credential.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
requirements.txt (1)

9-25: PyYAML is declared three times with inconsistent versions.

The file contains three conflicting PyYAML entries:

  • Line 9: PyYAML>=6.0.0
  • Line 21: pyyaml>=6.0.0
  • Line 25: PyYAML==6.0.3

This creates confusion and the pinned version (6.0.3) will conflict with the flexible constraints. Consolidate to a single entry.

🔎 Suggested fix
 # Configuration
 PyYAML>=6.0.0

 # Environment variable loading from .env files
 python-dotenv>=1.0.0

 # Encryption for environment variable secrets
 cryptography>=42.0.0

 # Terminal UI
 rich>=13.0.0

-# Configuration
-pyyaml>=6.0.0
-
 # Type hints for older Python versions
 typing-extensions>=4.0.0
-PyYAML==6.0.3
cortex/cli.py (1)

1243-1253: Remove duplicate notify row from rich help.

show_rich_help() currently adds notify twice and env once:

table.add_row("notify", "Manage desktop notifications")
table.add_row("env", "Manage environment variables")
table.add_row("notify", "Manage desktop notifications")

Drop the duplicate notify row so the help table stays clean:

-    table.add_row("notify", "Manage desktop notifications")
-    table.add_row("env", "Manage environment variables")
-    table.add_row("notify", "Manage desktop notifications")
+    table.add_row("notify", "Manage desktop notifications")
+    table.add_row("env", "Manage environment variables")
🧹 Nitpick comments (9)
examples/parallel_llm_demo.py (1)

215-219: Awkward multi-line formatting with stray empty lines.

The print statement spans multiple lines with seemingly empty lines inside the parentheses, which hurts readability. Consider consolidating or using proper line continuation.

🔎 Suggested cleanup
     print(
-        
-        f"   Time saved: {elapsed_seq - elapsed_par:.2f}s ({((elapsed_seq - elapsed_par) / elapsed_seq * 100):.1f}%)"
-    
+        f"   Time saved: {elapsed_seq - elapsed_par:.2f}s "
+        f"({((elapsed_seq - elapsed_par) / elapsed_seq * 100):.1f}%)"
     )
test_parallel_llm.py (2)

146-149: Accessing private semaphore attribute is fragile.

router._rate_limit_semaphore._value relies on internal implementation details. If the router's rate-limiting mechanism changes, this test will break silently or give misleading results.

Consider either exposing a public method to query rate limit state or simply verifying the behavior (e.g., that 5 requests complete without error when limited to 2 concurrent).


17-18: The sys.path insertion is functional but unclear; prefer explicit absolute path.

The current code with "." works correctly since the file is at the repository root and the cortex module is importable. However, the approach is ambiguous and relies on path normalization. Use an explicit absolute path approach instead:

 # Add parent directory to path
-sys.path.insert(0, os.path.join(os.path.dirname(__file__), "."))
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

This is clearer, more maintainable, and remains functionally equivalent.

cortex/env_manager.py (3)

77-86: from_dict may raise unclear KeyError if data is malformed.

If data["key"] or data["value"] is missing, a raw KeyError will propagate without context. Consider validating required keys or wrapping in a more descriptive error.

🔎 Optional defensive check
     @classmethod
     def from_dict(cls, data: dict[str, Any]) -> EnvironmentVariable:
         """Create from dictionary."""
+        if "key" not in data or "value" not in data:
+            raise ValueError("EnvironmentVariable requires 'key' and 'value' fields")
         return cls(
             key=data["key"],
             value=data["value"],
             encrypted=data.get("encrypted", False),
             description=data.get("description", ""),
             var_type=data.get("var_type", "string"),
         )

536-542: is_key_available() has a side effect: it may create the key file.

The method name suggests a read-only check, but _ensure_key_exists() will create the key if it doesn't exist. This could be surprising to callers who only want to query availability.

Consider renaming to ensure_key_available() or checking self.key_path.exists() first before calling _ensure_key_exists().


910-914: Quoted value parsing doesn't handle escape sequences.

The .env import strips outer quotes but doesn't process escape sequences like \" or \n. Values containing escaped quotes or newlines will be imported incorrectly.

Consider using a library like python-dotenv for parsing, or document this limitation:

# Note: Escape sequences within quoted values are not processed
docs/NETWORK_CONFIG.md (1)

25-58: Add code-fence languages to satisfy markdownlint (MD040).

Several fenced blocks use bare triple backticks (architecture diagram at line 25, cache path at line 281, apt proxy path at line 316, apt proxy contents at line 321). markdownlint (MD040) flags these.

Recommend annotating them with a language, e.g.:

-```
+```text
┌─────────────────────────────────────────────────────────────────┐
...

and similarly ```text for the cache path and apt-related blocks.

This keeps rendering unchanged while making linters and editors happier.

Also applies to: 281-326

tests/test_env_manager.py (1)

850-866: Avoid writing to the real $HOME in TestGetEnvManager tests.

test_get_env_manager_uses_default_paths asserts default paths under Path.home() / ".cortex", which means the tests will touch the caller’s actual home dir (~/.cortex/environments, ~/.cortex/.env_key).

To keep tests hermetic and side‑effect free, consider patching Path.home() to temp_dir in this test (or using a fixture) so you still validate the default-relative paths without hitting the real filesystem.

cortex/network_config.py (1)

257-290: Ensure TCP socket is always closed in check_connectivity.

check_connectivity() creates a raw socket.socket, calls connect(), and only closes it on the success path:

sock = socket.socket(...)
sock.settimeout(timeout)
sock.connect(("8.8.8.8", 53))
sock.close()

If connect() raises, the function falls through to the HTTP fallback without closing the socket. It will be cleaned up eventually, but using a context manager or finally is safer and more idiomatic:

-        try:
-            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            sock.settimeout(timeout)
-            sock.connect(("8.8.8.8", 53))
-            sock.close()
-            return True
-        except OSError:
+        try:
+            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+                sock.settimeout(timeout)
+                sock.connect(("8.8.8.8", 53))
+                return True
+        except OSError:
             # Connection failed - try HTTP endpoints
             pass

Not critical here, but it avoids leaking sockets under repeated failures.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de7f775 and 62abc66.

📒 Files selected for processing (34)
  • cortex/cli.py
  • cortex/coordinator.py
  • cortex/env_manager.py
  • cortex/first_run_wizard.py
  • cortex/hardware_detection.py
  • cortex/installation_history.py
  • cortex/installation_verifier.py
  • cortex/kernel_features/accelerator_limits.py
  • cortex/kernel_features/kv_cache_manager.py
  • cortex/kernel_features/model_lifecycle.py
  • cortex/logging_system.py
  • cortex/network_config.py
  • cortex/progress_indicators.py
  • cortex/progress_tracker.py
  • cortex/transaction_history.py
  • docs/ENV_MANAGEMENT.md
  • docs/NETWORK_CONFIG.md
  • docs/NETWORK_CONFIG_TESTS.md
  • examples/env_demo.py
  • examples/parallel_llm_demo.py
  • examples/progress_demo.py
  • examples/standalone_demo.py
  • requirements.txt
  • src/intent/llm_agent.py
  • src/intent/planner.py
  • test_parallel_llm.py
  • tests/test_cli.py
  • tests/test_coordinator.py
  • tests/test_env_manager.py
  • tests/test_interpreter.py
  • tests/test_llm_router.py
  • tests/test_logging_system.py
  • tests/test_network_config.py
  • tests/unit/test_progress_tracker.py
💤 Files with no reviewable changes (4)
  • tests/test_coordinator.py
  • src/intent/llm_agent.py
  • tests/test_interpreter.py
  • src/intent/planner.py
✅ Files skipped from review due to trivial changes (14)
  • cortex/hardware_detection.py
  • cortex/logging_system.py
  • cortex/progress_indicators.py
  • cortex/kernel_features/kv_cache_manager.py
  • examples/standalone_demo.py
  • cortex/kernel_features/accelerator_limits.py
  • cortex/kernel_features/model_lifecycle.py
  • docs/ENV_MANAGEMENT.md
  • examples/progress_demo.py
  • tests/test_llm_router.py
  • cortex/installation_history.py
  • cortex/coordinator.py
  • cortex/progress_tracker.py
  • tests/test_logging_system.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_cli.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • tests/test_env_manager.py
  • test_parallel_llm.py
  • tests/unit/test_progress_tracker.py
  • cortex/installation_verifier.py
  • cortex/env_manager.py
  • cortex/network_config.py
  • cortex/first_run_wizard.py
  • cortex/cli.py
  • tests/test_network_config.py
  • cortex/transaction_history.py
  • examples/env_demo.py
  • examples/parallel_llm_demo.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_env_manager.py
  • tests/unit/test_progress_tracker.py
  • tests/test_network_config.py
**/*install*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*install*.py: Dry-run by default for all installations in command execution
No silent sudo execution - require explicit user confirmation
Implement audit logging to ~/.cortex/history.db for all package operations

Files:

  • cortex/installation_verifier.py
🧠 Learnings (1)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*.py : Docstrings required for all public APIs

Applied to files:

  • cortex/first_run_wizard.py
🧬 Code graph analysis (6)
tests/test_env_manager.py (1)
cortex/env_manager.py (36)
  • EncryptionManager (454-542)
  • EnvironmentManager (665-1105)
  • EnvironmentStorage (545-662)
  • EnvironmentTemplate (102-125)
  • EnvironmentValidator (372-451)
  • EnvironmentVariable (58-86)
  • TemplateVariable (90-98)
  • ValidationResult (129-134)
  • VariableType (46-54)
  • get_env_manager (1108-1110)
  • to_dict (67-75)
  • to_dict (109-115)
  • from_dict (78-86)
  • from_dict (118-125)
  • validate (392-451)
  • encrypt (502-514)
  • decrypt (516-534)
  • is_key_available (536-542)
  • save (597-631)
  • load (571-595)
  • delete_app (633-648)
  • list_apps (650-662)
  • list_apps (828-835)
  • set_variable (689-738)
  • get_variable (740-767)
  • get_variable_info (769-781)
  • list_variables (783-794)
  • delete_variable (796-814)
  • clear_app (816-826)
  • export_env (837-870)
  • import_env (872-932)
  • load_to_environ (934-956)
  • list_templates (960-967)
  • get_template (969-979)
  • apply_template (981-1061)
  • validate_app (1063-1105)
test_parallel_llm.py (1)
cortex/llm_router.py (8)
  • LLMRouter (75-695)
  • TaskType (32-42)
  • check_hardware_configs_parallel (828-876)
  • diagnose_errors_parallel (779-825)
  • query_multiple_packages (729-776)
  • acomplete (445-509)
  • complete_batch (614-695)
  • set_rate_limit (436-443)
cortex/env_manager.py (1)
tests/test_env_manager.py (1)
  • storage (51-53)
cortex/first_run_wizard.py (1)
cortex/utils/api_key_validator.py (2)
  • validate_anthropic_api_key (10-28)
  • validate_openai_api_key (34-48)
tests/test_network_config.py (1)
cortex/network_config.py (19)
  • add_proxy_auth (586-613)
  • check_proxy_auth (544-570)
  • prompt_proxy_credentials (573-583)
  • _detect_env_proxy (129-136)
  • _detect_gnome_proxy (138-180)
  • _detect_system_proxy (182-214)
  • _parse_proxy_url (216-218)
  • detect_vpn (222-253)
  • check_connectivity (257-289)
  • detect_network_quality (291-307)
  • configure_apt_proxy (311-347)
  • configure_pip_proxy (349-362)
  • get_httpx_proxy_config (364-384)
  • cleanup_apt_proxy (460-477)
  • cache_package_list (388-408)
  • get_cached_packages (410-443)
  • enable_offline_fallback (445-458)
  • auto_configure (479-516)
  • print_summary (520-538)
examples/parallel_llm_demo.py (1)
cortex/llm_router.py (1)
  • query_multiple_packages (729-776)
🪛 markdownlint-cli2 (0.18.1)
docs/NETWORK_CONFIG.md

25-25: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


281-281: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


316-316: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


321-321: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/NETWORK_CONFIG_TESTS.md

30-30: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (12)
tests/unit/test_progress_tracker.py (1)

491-491: LGTM! PEP 8 formatting improvement.

The addition of spaces around the + operator improves PEP 8 compliance and aligns the test expectations with the updated progress tracker formatting.

cortex/transaction_history.py (1)

359-361: LGTM!

The consolidation of the rollback warning message into a single f-string improves readability without changing behavior.

test_parallel_llm.py (1)

1-26: New test script for parallel LLM calls looks well-structured.

The script provides good coverage of async completion, batch processing, rate limiting, and helper functions. The API key checks before running tests and the summary at the end are helpful.

As per coding guidelines, consider adding docstrings to all public functions (currently only brief docstrings are present).

cortex/env_manager.py (2)

612-631: Atomic write implementation is well done.

Using tempfile.mkstemp followed by os.replace ensures atomic updates to app environment files. Good defensive programming with cleanup in the except block.


1-12: Comprehensive environment management module with solid design.

The module provides well-structured components for per-app environment storage, encryption, validation, and templating. The lazy loading of cryptography and atomic file operations are good practices.

Consider adding an __all__ export list to explicitly define the public API surface.

examples/env_demo.py (2)

25-26: Path manipulation for example script is acceptable.

The sys.path.insert to enable imports from the parent directory is a common pattern for example scripts that aren't installed as packages.


378-427: Well-structured demo with proper cleanup.

The demo uses a temporary directory for storage, preventing any persistent side effects. The cleanup of test environment variables in demo_load_to_environ is good practice. The Rich-based UI provides clear, readable output.

cortex/installation_verifier.py (1)

311-313: LGTM!

The partial verification message consolidation is a minor cleanup with no functional change.

docs/NETWORK_CONFIG_TESTS.md (1)

1-31: Comprehensive test documentation with clear structure.

The documentation provides excellent coverage of the test suite organization, individual test descriptions, and guidance for running tests and adding new ones. The tables are well-formatted and informative.

cortex/cli.py (2)

868-1224: Env subcommand wiring and error handling look solid.

The new env entrypoint and helpers (_env_set/_env_get/_env_list/_env_delete/_env_export/_env_import/_env_clear/_env_template*, _env_list_apps, _env_load) are consistently typed, respect EnvironmentManager’s API, and handle common error conditions (ValueError/OSError, missing files, decryption failures) without crashing the CLI. The non‑zero exit codes on failure paths are appropriate.


1285-1307: Network auto‑config is safely best‑effort.

Creating NetworkConfig(auto_detect=False) and only calling detect(check_quality=True)/auto_configure() for network‑sensitive commands, all wrapped in a broad try/except that only warns on failure, is a good compromise between functionality and startup robustness. This shouldn’t block the CLI even if detection fails.

tests/test_network_config.py (1)

26-691: Comprehensive coverage of NetworkConfig behaviors.

This suite exercises all major paths in NetworkConfig and its helpers: proxy/VPN detection, connectivity and quality checks, apt/pip/httpx configuration, caching/offline behavior, and integration flows. The mocking of subprocess, socket, requests, and filesystem access matches the implementation details in cortex/network_config.py, so regressions here should be caught early.

Comment on lines +146 to +179
def get_valid_api_key(env_var: str, key_type: str = "generic") -> str | None:
"""
Get a valid API key from .env file first, then environment variable.
Treats blank keys as missing.
"""
key_from_file = read_key_from_env_file(env_var)

env_path = get_env_file_path()
logger.debug(f"Checking {env_var} in {env_path}: '{key_from_file}'")

if key_from_file is not None and len(key_from_file) > 0:
if is_valid_api_key(key_from_file, key_type):
os.environ[env_var] = key_from_file
return key_from_file
return None

if env_var in os.environ:
del os.environ[env_var]

return None


def detect_available_providers() -> list[str]:
"""Detect available providers based on valid (non-blank) API keys in .env file."""
providers = []

if get_valid_api_key("ANTHROPIC_API_KEY", "anthropic"):
providers.append("anthropic")
if get_valid_api_key("OPENAI_API_KEY", "openai"):
providers.append("openai")
if shutil.which("ollama"):
providers.append("ollama")

return providers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

wc -l cortex/first_run_wizard.py

Repository: cortexlinux/cortex

Length of output: 92


🏁 Script executed:

head -200 cortex/first_run_wizard.py | cat -n

Repository: cortexlinux/cortex

Length of output: 7205


🏁 Script executed:

sed -n '146,179p' cortex/first_run_wizard.py | cat -n

Repository: cortexlinux/cortex

Length of output: 1389


🏁 Script executed:

sed -n '378,417p' cortex/first_run_wizard.py | cat -n

Repository: cortexlinux/cortex

Length of output: 1685


🏁 Script executed:

# Find helper function definitions
rg -n "def (read_key_from_env_file|is_valid_api_key|get_env_file_path|save_key_to_env_file|detect_available_providers|run)" cortex/first_run_wizard.py

Repository: cortexlinux/cortex

Length of output: 439


🏁 Script executed:

# Verify the exact behavior: check if env var is actually deleted when .env is missing
sed -n '146,166p' cortex/first_run_wizard.py | cat -n

Repository: cortexlinux/cortex

Length of output: 868


🏁 Script executed:

# Also check if there are any other calls to get_valid_api_key that might be relevant
rg -n "get_valid_api_key|detect_available_providers" cortex/first_run_wizard.py

Repository: cortexlinux/cortex

Length of output: 539


get_valid_api_key() contradicts its docstring and deletes environment-only API keys instead of honoring them.

The function's docstring (lines 147-149) states: "Get a valid API key from .env file first, then environment variable. Treats blank keys as missing." However, the implementation only consults the .env file and deletes any existing os.environ[env_var] when the file does not contain a non-blank value:

if key_from_file is not None and len(key_from_file) > 0:
    if is_valid_api_key(key_from_file, key_type):
        os.environ[env_var] = key_from_file
        return key_from_file
    return None

if env_var in os.environ:
    del os.environ[env_var]  # ← Deletes env-only keys
return None

Additionally, run() (lines 23-27) unconditionally deletes OPENAI_API_KEY and ANTHROPIC_API_KEY from os.environ when the .env file has those keys blank or missing, and detect_available_providers() relies solely on get_valid_api_key() for detection.

As a result, users who export API keys in their shell environment (the scenario in issue #355) cannot use the wizard—their keys are not recognized and are actively deleted from the environment. To fix this:

  1. Check .env first and sync valid keys to os.environ
  2. Fall back to os.environ if .env has no valid key
  3. Only clear env vars if both sources are invalid

Example fix:

def get_valid_api_key(env_var: str, key_type: str = "generic") -> str | None:
     key_from_file = read_key_from_env_file(env_var)
-    ...
-    if key_from_file is not None and len(key_from_file) > 0:
-        if is_valid_api_key(key_from_file, key_type):
-            os.environ[env_var] = key_from_file
-            return key_from_file
-        return None
-
-    if env_var in os.environ:
-        del os.environ[env_var]
-
-    return None
+    if key_from_file and is_valid_api_key(key_from_file, key_type):
+        os.environ[env_var] = key_from_file
+        return key_from_file
+
+    env_val = os.environ.get(env_var)
+    if env_val and is_valid_api_key(env_val, key_type):
+        return env_val
+
+    os.environ.pop(env_var, None)
+    return None

Remove the unconditional env var deletions in run().

🤖 Prompt for AI Agents
In cortex/first_run_wizard.py around lines 146-179, get_valid_api_key currently
only checks the .env file and deletes environment-only keys, contradicting its
docstring and breaking users who export keys in their shell; change the logic
to: read .env first and if a valid key exists, sync it to os.environ and return
it; if .env has no valid key, check os.environ and if the environment key is
valid return it (do not delete it); only remove/clear the env var if both .env
and os.environ have no valid key; also remove the unconditional deletions of
OPENAI_API_KEY and ANTHROPIC_API_KEY in run() and ensure
detect_available_providers continues to call get_valid_api_key to detect
providers.

Comment on lines 225 to 229
## Related Documentation

- [Network Configuration Module](./modules/network_config.md)
- [Graceful Degradation](./GRACEFUL_DEGRADATION.md)
- [Troubleshooting](./TROUBLESHOOTING.md)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if the referenced documentation path exists
fd -t f "network_config.md" docs/

Repository: cortexlinux/cortex

Length of output: 84


🏁 Script executed:

# Also search for NETWORK_CONFIG files
fd -t f "NETWORK_CONFIG" docs/

Repository: cortexlinux/cortex

Length of output: 113


🏁 Script executed:

# Search for references to these paths in the documentation
rg "network_config|NETWORK_CONFIG" docs/ -t md

Repository: cortexlinux/cortex

Length of output: 1503


Fix the broken documentation link.

The link to ./modules/network_config.md references a non-existent path. The correct reference is ./NETWORK_CONFIG.md (same level as this file). This is confirmed by the early reference in the same file that correctly uses ./NETWORK_CONFIG.md.

🤖 Prompt for AI Agents
In docs/NETWORK_CONFIG_TESTS.md around lines 225 to 229, the "Network
Configuration Module" link points to ./modules/network_config.md which doesn't
exist; update that link to ./NETWORK_CONFIG.md (same directory as this file) so
it matches the correct earlier reference and resolves the broken link.

Comment on lines +40 to 41
responses = await query_multiple_packages(router, packages, max_concurrent=5)
responses = await query_multiple_packages(router, packages, max_concurrent=5)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Duplicate async call wastes API quota and skews timing measurements.

The same query_multiple_packages call is executed twice in sequence—only the second result is used while the first is discarded. This doubles API costs and inflates the measured elapsed time, undermining the demo's purpose of showing parallel speedup.

🔎 Proposed fix
-    responses = await query_multiple_packages(router, packages, max_concurrent=5)
     responses = await query_multiple_packages(router, packages, max_concurrent=5)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
responses = await query_multiple_packages(router, packages, max_concurrent=5)
responses = await query_multiple_packages(router, packages, max_concurrent=5)
responses = await query_multiple_packages(router, packages, max_concurrent=5)
🤖 Prompt for AI Agents
In examples/parallel_llm_demo.py around lines 40-41, there's a duplicated await
of query_multiple_packages that calls the API twice but only uses the second
result; remove the redundant call so the function is awaited only once (or if
you intended two runs, store results separately and measure each run
independently), and update any timing/elapsed calculation to reflect the single
intended call to avoid doubled API usage and incorrect speed measurements.

Comment on lines 14 to 16
# Encryption for environment variable secrets
cryptography>=42.0.0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n requirements.txt

Repository: cortexlinux/cortex

Length of output: 647


🌐 Web query:

cryptography library latest version 2024 2025

💡 Result:

If you mean the Python "cryptography" (pyca/cryptography) package:

  • Latest (current) release: v46.0.3 — released Oct 15, 2025. [1][2]
  • Key 2025 releases: v45.0.0 (May 17, 2025), v46.0.0 (Sep 16, 2025). [2]
  • Key 2024 release: v44.0.0 — released Nov 27, 2024. [2]

Sources: PyPI release page for cryptography [1]; release history summary. [2]

References
[1] PyPI: cryptography (release files & dates).
[2] Release history listing for cryptography (versions/dates).


Cryptography dependency is appropriate, but requirements.txt has conflicting PyYAML entries.

The cryptography>=42.0.0 dependency supports Fernet-based encryption. Version 42.0.0 (Dec 2023) is a reasonable minimum that allows for newer patch and minor releases (current is v46.0.3).

However, requirements.txt contains three conflicting PyYAML entries that must be consolidated:

  • Line 9: PyYAML>=6.0.0
  • Line 21: pyyaml>=6.0.0 (lowercase casing)
  • Line 25: PyYAML==6.0.3 (pinned version)

Remove duplicates and resolve the version constraint conflict (decide between >=6.0.0 or ==6.0.3).

🤖 Prompt for AI Agents
In requirements.txt around lines 14 to 16, consolidate the conflicting PyYAML
entries: remove the duplicate lines (the lowercase "pyyaml>=6.0.0" and the other
"PyYAML>=6.0.0") and keep a single, consistently-cased entry for PyYAML pinned
to a specific version for reproducibility (replace remaining entry with
"PyYAML==6.0.3"), or if you prefer loose constraints use one entry
"PyYAML>=6.0.0"—but do not keep multiple entries or differing casings.

@jaysurse jaysurse force-pushed the fix/cortex-wizard-not-running branch from 62abc66 to 6dbe41d Compare December 25, 2025 11:34
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (3)
examples/parallel_llm_demo.py (1)

40-41: Duplicate async call wastes API quota and skews timing measurements.

The same query_multiple_packages call is executed twice in sequence—only the second result is used while the first is discarded. Remove line 40 to execute the function once.

🔎 Proposed fix
-    responses = await query_multiple_packages(router, packages, max_concurrent=5)
     responses = await query_multiple_packages(router, packages, max_concurrent=5)
cortex/cli.py (1)

66-81: Provider/key mismatch risk remains from previous review.

The fallback logic at lines 73-76 can still return "ollama-local" while _get_provider() returns "openai" or "claude" when CORTEX_PROVIDER is explicitly set but the corresponding API key is missing. This causes the mismatch flagged in the previous review.

Consider restricting the ollama-local fallback to only when provider == "ollama" (remove the or not (...) clause) to ensure provider and key remain consistent.

cortex/first_run_wizard.py (1)

21-22: Remove unused API key validator imports.

The validate_anthropic_api_key and validate_openai_api_key functions are imported but never called. The wizard only performs prefix validation (is_valid_api_key checks startswith) without verifying that keys actually work with the respective APIs.

Either remove these unused imports or integrate them into the key validation flow (e.g., in _prompt_for_api_key or during the dry-run verification) to catch invalid keys earlier.

🔎 Remove unused imports
-# Import API key validation utilities
-from cortex.utils.api_key_validator import validate_anthropic_api_key, validate_openai_api_key
-
🧹 Nitpick comments (4)
test_parallel_llm.py (1)

29-252: Consider pytest integration if this becomes part of the test suite.

The test functions return booleans and print results, which works for standalone execution but doesn't integrate with pytest's reporting. If you move this to tests/, consider:

  • Using assert statements instead of returning booleans
  • Using pytest.skip() for missing API keys instead of conditional returns
  • Leveraging pytest fixtures for shared setup (router initialization)
cortex/utils/api_key_validator.py (1)

10-48: API key validation makes real API calls—ensure users understand the implications.

Both validation functions make actual HTTP requests to provider APIs. This is appropriate for validation but means:

  • Validation has latency (10s timeout per provider)
  • Invalid keys consume network resources
  • Validation may fail due to network issues, not just invalid keys

Consider documenting this behavior if these functions are called frequently or in performance-sensitive paths.

cortex/cli.py (1)

869-871: Remove redundant import.

sys is already imported at line 4. The re-import at line 871 is unnecessary.

🔎 Proposed fix
     def env(self, args: argparse.Namespace) -> int:
         """Handle environment variable management commands."""
-        import sys
-
         env_mgr = get_env_manager()
cortex/first_run_wizard.py (1)

463-561: Consider extracting common provider setup logic.

The Anthropic (lines 463-512) and OpenAI (lines 513-561) setup blocks contain nearly identical logic:

  • Check for existing key via get_valid_api_key()
  • Prompt to replace or keep existing key
  • Prompt for new key if missing
  • Save to .env or shell config fallback
  • Run dry-run verification

Consider extracting a helper method like _setup_api_provider(provider_name, env_var, key_type, forced_provider) to reduce ~90 lines of duplication and improve maintainability.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62abc66 and f8ac7e5.

📒 Files selected for processing (13)
  • .env.example
  • cortex/cli.py
  • cortex/env_loader.py
  • cortex/first_run_wizard.py
  • cortex/utils/api_key_validator.py
  • examples/env_demo.py
  • examples/parallel_llm_demo.py
  • test_parallel_llm.py
  • tests/installer/test_parallel_install.py
  • tests/test_cli.py
  • tests/test_cli_extended.py
  • tests/test_env_manager.py
  • tests/test_first_run_wizard.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/test_cli.py
  • tests/test_env_manager.py
  • tests/installer/test_parallel_install.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/env_loader.py
  • tests/test_first_run_wizard.py
  • test_parallel_llm.py
  • tests/test_cli_extended.py
  • cortex/cli.py
  • cortex/utils/api_key_validator.py
  • cortex/first_run_wizard.py
  • examples/env_demo.py
  • examples/parallel_llm_demo.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_first_run_wizard.py
  • tests/test_cli_extended.py
🧠 Learnings (1)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*.py : Docstrings required for all public APIs

Applied to files:

  • cortex/first_run_wizard.py
🧬 Code graph analysis (5)
tests/test_first_run_wizard.py (1)
cortex/first_run_wizard.py (1)
  • _step_api_setup (640-701)
test_parallel_llm.py (1)
cortex/llm_router.py (6)
  • LLMRouter (75-695)
  • TaskType (32-42)
  • check_hardware_configs_parallel (828-876)
  • diagnose_errors_parallel (779-825)
  • query_multiple_packages (729-776)
  • complete_batch (614-695)
tests/test_cli_extended.py (2)
tests/test_cli.py (2)
  • test_get_api_key_not_found (31-34)
  • test_install_no_api_key (62-64)
cortex/cli.py (2)
  • _get_api_key (66-81)
  • install (347-610)
cortex/first_run_wizard.py (1)
cortex/utils/api_key_validator.py (2)
  • validate_anthropic_api_key (10-28)
  • validate_openai_api_key (34-48)
examples/parallel_llm_demo.py (1)
cortex/llm_router.py (1)
  • query_multiple_packages (729-776)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 5-5: [UnorderedKey] The ANTHROPIC_API_KEY key should go before the OPENAI_API_KEY key

(UnorderedKey)

🪛 GitHub Actions: CI
examples/env_demo.py

[error] 1-1: Ruff check failed: I001 Import block is un-sorted or un-formatted.

🪛 GitHub Check: lint
examples/env_demo.py

[failure] 1-1: Ruff (I001)
examples/env_demo.py:1:1: I001 Import block is un-sorted or un-formatted

examples/parallel_llm_demo.py

[failure] 218-218: Ruff (W293)
examples/parallel_llm_demo.py:218:1: W293 Blank line contains whitespace


[failure] 216-216: Ruff (W293)
examples/parallel_llm_demo.py:216:1: W293 Blank line contains whitespace

🪛 GitHub Check: Lint
examples/env_demo.py

[failure] 1-1: Ruff (I001)
examples/env_demo.py:1:1: I001 Import block is un-sorted or un-formatted

examples/parallel_llm_demo.py

[failure] 218-218: Ruff (W293)
examples/parallel_llm_demo.py:218:1: W293 Blank line contains whitespace


[failure] 216-216: Ruff (W293)
examples/parallel_llm_demo.py:216:1: W293 Blank line contains whitespace

🪛 Gitleaks (8.30.0)
tests/test_first_run_wizard.py

[high] 269-269: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
🔇 Additional comments (9)
cortex/env_loader.py (1)

32-56: Expanded .env search locations improve environment-driven configuration.

The addition of parent directory and cortex package directory search locations, along with safe ImportError handling, aligns well with the PR's environment-driven configuration goals.

.env.example (1)

1-5: Example .env file supports environment-driven configuration.

The sample environment file clearly demonstrates the expected API key format and aligns with the PR's environment-driven configuration approach.

tests/test_first_run_wizard.py (1)

261-287: Test coverage expanded for multi-provider API key detection.

The addition of test_step_api_setup_existing_openai_key and the update to test_step_api_setup_no_key properly test the environment-driven provider detection logic introduced in this PR.

Note: The Gitleaks warning on line 269 is a false positive—this is a test fixture, not a real secret.

tests/test_cli_extended.py (2)

33-83: Test updates properly reflect environment-driven API key resolution.

The updates to test expectations (returning "ollama-local" instead of None, and returning 0 instead of 1 for install without explicit API keys) correctly align with the new provider-aware fallback logic introduced in this PR.


85-125: Environment variable patches support new configuration flow.

The addition of @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-key"}, clear=True) decorators throughout these tests properly isolates test execution and validates the environment-driven provider selection logic.

cortex/cli.py (3)

50-64: Provider-specific API key retrieval with format validation.

The new _get_api_key_for_provider method cleanly separates provider-specific key logic and includes defensive format validation for Claude and OpenAI keys.


374-420: Provider probing with fallback improves resilience.

The provider probing loop with fallback to alternative providers (lines 387-413) enhances resilience when one provider fails. The exception handling now properly catches all exceptions, addressing the previous review concern.


869-1218: Comprehensive environment variable management implementation.

The new env method with its extensive subcommand surface (set/get/list/delete/export/import/clear/template/apps/load) is well-structured with clean separation of concerns via helper methods. Error handling is consistent throughout.

cortex/utils/api_key_validator.py (1)

10-28: Add type hint for return value.

Per coding guidelines, type hints are required in Python code. Add -> bool to the function signature.

🔎 Proposed fix
-def validate_anthropic_api_key(api_key: str) -> bool:
+def validate_anthropic_api_key(api_key: str) -> bool:

Wait, the signature already has -> bool based on the AI summary. Let me check the actual code...

Looking at the annotated code, line 10 shows:

def validate_anthropic_api_key(api_key: str) -> bool:

This already has the type hint. I should skip this comment.

Likely an incorrect or invalid review comment.

Comment on lines 1244 to +1115
table.add_row("notify", "Manage desktop notifications")
table.add_row("env", "Manage environment variables")
table.add_row("notify", "Manage desktop notifications")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove duplicate 'notify' entry in help table.

Line 1246 duplicates the "notify" command already shown at line 1244. Remove the duplicate entry.

🔎 Proposed fix
     table.add_row("rollback <id>", "Undo installation")
     table.add_row("notify", "Manage desktop notifications")
     table.add_row("env", "Manage environment variables")
-    table.add_row("notify", "Manage desktop notifications")
     table.add_row("cache stats", "Show LLM cache statistics")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
table.add_row("notify", "Manage desktop notifications")
table.add_row("env", "Manage environment variables")
table.add_row("notify", "Manage desktop notifications")
table.add_row("rollback <id>", "Undo installation")
table.add_row("notify", "Manage desktop notifications")
table.add_row("env", "Manage environment variables")
table.add_row("cache stats", "Show LLM cache statistics")
🤖 Prompt for AI Agents
In cortex/cli.py around lines 1244-1246, the help table contains a duplicate
entry for "notify" (lines 1244 and 1246). Remove the redundant
table.add_row("notify", "Manage desktop notifications") call (the second
occurrence) so the help table only lists "notify" once; keep the other entries
(e.g., "env") unchanged.

Comment on lines 1536 to +1390
elif args.command == "env":
return cli.env(args)
elif args.command == "env":
return cli.env(args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove duplicate 'env' command dispatch.

Lines 1538-1539 duplicate the "env" command handling from lines 1536-1537. Remove the duplicate elif block.

🔎 Proposed fix
         elif args.command == "env":
             return cli.env(args)
-        elif args.command == "env":
-            return cli.env(args)
         else:
             parser.print_help()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
elif args.command == "env":
return cli.env(args)
elif args.command == "env":
return cli.env(args)
elif args.command == "env":
return cli.env(args)
else:
parser.print_help()
🤖 Prompt for AI Agents
In cortex/cli.py around lines 1536 to 1539 there is a duplicated elif block
handling args.command == "env". Remove the redundant second elif (lines
1538-1539) so the command dispatch only calls and returns cli.env(args) once;
ensure no extra blank lines remain and run tests/lint to confirm no formatting
or unreachable-code issues.

Comment on lines 1 to 2
from __future__ import annotations
#!/usr/bin/env python3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the import ordering: shebang must be first.

The from __future__ import annotations import is placed before the shebang, which violates Python syntax requirements and PEP 8. The shebang (#!/usr/bin/env python3) must be the absolute first line of the file, followed by the module docstring, then from __future__ imports, then other imports.

This is causing the CI pipeline to fail with Ruff I001 error.

🔎 Proposed fix to correct the import ordering
-from __future__ import annotations
-
 #!/usr/bin/env python3
 """
 Environment Variable Manager Demo
 
 This script demonstrates the environment variable management features
 of Cortex, including:
 
 - Setting and getting variables
 - Encrypting sensitive values
 - Using templates
 - Importing and exporting
 - Variable validation
 
 Run this script to see the env manager in action:
     python examples/env_demo.py
 """
 
+from __future__ import annotations
+
 import os
 import sys
 import tempfile
🧰 Tools
🪛 GitHub Actions: CI

[error] 1-1: Ruff check failed: I001 Import block is un-sorted or un-formatted.

🪛 GitHub Check: lint

[failure] 1-1: Ruff (I001)
examples/env_demo.py:1:1: I001 Import block is un-sorted or un-formatted

🪛 GitHub Check: Lint

[failure] 1-1: Ruff (I001)
examples/env_demo.py:1:1: I001 Import block is un-sorted or un-formatted

🤖 Prompt for AI Agents
In examples/env_demo.py around lines 1-2, the shebang and future import are
reversed causing a Ruff I001 error; move the shebang (#!/usr/bin/env python3) to
be the very first line of the file, then place any module docstring (if
present), then the from __future__ import annotations line, followed by the rest
of imports so the file follows Python syntax and PEP 8 ordering.

Comment on lines +1 to +27
#!/usr/bin/env python3
"""
Quick test script to verify parallel LLM calls are working.
Run this to test:
1. Async completion works
2. Batch processing works
3. Rate limiting works
4. Helper functions work
"""

import asyncio
import os
import sys
import time

# Add parent directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "."))

from cortex.llm_router import (
LLMRouter,
TaskType,
check_hardware_configs_parallel,
diagnose_errors_parallel,
query_multiple_packages,
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Clarify file location: test suite vs. standalone demo.

This file is located at the repository root, which is unconventional for test files. Consider:

  • If this is part of the test suite, move it to tests/test_parallel_llm.py and integrate with pytest.
  • If this is a standalone demo or verification script, move it to examples/ or scripts/.

The current location may cause confusion about its purpose and whether it's executed by CI.

🤖 Prompt for AI Agents
In test_parallel_llm.py lines 1-27: the file sits at the repo root which
confuses its intent (test vs demo) and may be picked up by CI; if this is a test
move it to tests/test_parallel_llm.py and convert it to a pytest-compatible test
(use assertions, fixtures, and avoid top-level execution), otherwise move it to
examples/ or scripts/ (e.g., examples/parallel_llm_demo.py) and keep its
executable header and README usage notes; update imports/paths if moved and
adjust project config (pytest.ini or CI) to include or exclude the new location
accordingly.

- The wizard command was only showing a static message instead of running
- Now properly calls FirstRunWizard.run() for full interactive setup
- Wizard detects existing API keys and proceeds through all setup steps
Jay Surse added 24 commits December 29, 2025 11:16
- Update _get_api_key to return 'ollama-local' when no key found and provider is ollama or no cloud keys set
- Change default provider to 'ollama' when no API keys available
- Add skip option to wizard for subsequent runs
- Fix test mocking for install dry run test
- Format code with Black and ensure Ruff compliance

Resolves CI test failures for API key fallback and install commands.
- Add proper validation for existing OPENAI_API_KEY in wizard
- Test both format and API functionality before accepting keys
- Prevent prompting when valid key exists in environment
- Update test expectations for offline mode behavior
- Add missing Mock import in wizard tests

Fixes issue where wizard would ignore valid OpenAI API keys and repeatedly prompt for manual input.
- Add CommandInterpreter mock to test_install_no_api_key
- Ensures test passes when no API keys are available but Ollama fallback works
- Fixes test failure: 'No commands generated with any available provider'
- Remove @patch.object(CortexCLI, '_get_api_key', return_value=None)
- Test now relies on the fixed install() method that doesn't require API keys
- Ensures test passes when install works without API key validation
- Add load_env() call to ensure consistent API key loading from .env files
- Implement proper non-interactive mode that auto-configures without prompts
- Fix config file path handling to use instance paths instead of global paths
- Mark setup complete when user chooses to skip configuration
- All 47 tests pass with consistent behavior
- Remove auto-skip behavior when provider is already configured
- Always display provider selection menu with available options
- Add 'Keep current provider (skip setup)' option when re-running wizard
- Show current provider status clearly in menu
- Allow users to switch providers anytime via wizard
- Provider selection menu now always displays OpenAI, Anthropic, and Ollama options, regardless of API key presence.
- If a selected provider's API key is blank or missing in .env, the wizard prompts the user to enter and save a valid key.
- Ensures a consistent onboarding flow and allows users to set or update any provider at any time.
… collection

- Renamed cortex/utils/api_key_test.py to cortex/utils/api_key_validator.py
- Renamed test_anthropic_api_key to validate_anthropic_api_key
- Renamed test_openai_api_key to validate_openai_api_key
- Updated imports in first_run_wizard.py and test_first_run_wizard.py
- Fixes pytest fixture 'api_key' not found error
- Renamed cortex/utils/api_key_test.py to cortex/utils/api_key_validator.py
- Renamed test_* functions to validate_* to prevent pytest auto-discovery
- Updated all imports in first_run_wizard.py and tests
- All 47 tests now pass
- The wizard command was only showing a static message instead of running
- Now properly calls FirstRunWizard.run() for full interactive setup
- Wizard detects existing API keys and proceeds through all setup steps
- Always show menu letting user choose API provider
- Display '✓ (key found)' next to providers with existing keys
- User can now select provider even when keys already exist
- Only auto-select in non-interactive mode
@jaysurse jaysurse force-pushed the fix/cortex-wizard-not-running branch from f8ac7e5 to 82595c8 Compare December 29, 2025 05:48
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.env.example (1)

1-69: Resolve merge conflict markers and restore a single valid .env.example.

This file still contains Git conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...) and malformed keys, which breaks tooling (dotenv-linter) and is a merge blocker. Keep one canonical example block and remove the conflict section.

Proposed minimal fix (keep existing detailed template)
-<<<<<<< HEAD
 # Cortex Linux Environment Configuration
@@
 #   - /etc/cortex/.env (Linux only)
 #
-=======
-# Example .env file for Cortex
-# Copy this to .env and fill in your API keys
-
-OPENAI_API_KEY=sk-your-openai-key-here
-ANTHROPIC_API_KEY=sk-ant-your-anthropic-key-here
->>>>>>> 34c66df (feat: seamless onboarding—auto-create .env from .env.example, improved wizard flow)

If you still want a short example at the bottom, re-add it without conflict markers and ensure any sample keys don’t look like real secrets (e.g., OPENAI_API_KEY=test-openai-key).

♻️ Duplicate comments (8)
examples/parallel_llm_demo.py (2)

40-41: Duplicate async call wastes API quota and skews timing measurements.

The same query_multiple_packages call is executed twice in sequence—only the second result is used while the first is discarded. This doubles API costs and inflates the measured elapsed time, undermining the demo's purpose of showing parallel speedup.

🔎 Proposed fix
-    responses = await query_multiple_packages(router, packages, max_concurrent=5)
     responses = await query_multiple_packages(router, packages, max_concurrent=5)

216-218: Remove trailing whitespace on blank lines to pass linter.

Lines 216 and 218 contain trailing whitespace, causing W293 linter failures and blocking CI/CD pipeline.

🔎 Proposed fix
-        
+
     f"   Time saved: {elapsed_seq - elapsed_par:.2f}s ({((elapsed_seq - elapsed_par) / elapsed_seq * 100):.1f}%)"
-    
+
 )
test_parallel_llm.py (1)

1-314: Clarify role of this script and relocate accordingly.

This looks like a manual test/demo harness rather than a proper unit test module, but it lives at the repo root with a test_*.py name, which is confusing and may interact oddly with CI test discovery.

Consider:

  • Moving it under tests/ and converting to pytest/unittest style (no __main__, assertions instead of print-and-bool), or
  • Moving it to examples/ or scripts/ (e.g., examples/parallel_llm_demo.py) and treating it as a standalone demo.
cortex/cli.py (3)

1102-1116: Remove duplicate notify row from Rich help table.

show_rich_help() adds "notify" twice to the commands table (lines 1113 and 1115), which clutters the help output.

Suggested cleanup
     table.add_row("rollback <id>", "Undo installation")
     table.add_row("notify", "Manage desktop notifications")
     table.add_row("env", "Manage environment variables")
-    table.add_row("notify", "Manage desktop notifications")

1387-1390: Remove duplicate env command dispatch in main().

The elif args.command == "env": return cli.env(args) branch appears twice back-to-back, which is redundant and slightly obscures the control flow.

Proposed change
-        elif args.command == "env":
-            return cli.env(args)
-        elif args.command == "env":
-            return cli.env(args)
+        elif args.command == "env":
+            return cli.env(args)

41-67: Avoid returning an Ollama key for non-Ollama providers.

_get_api_key() can return "ollama-local" even when _get_provider() is "openai" or "claude" and no API keys are set, because of the or not (OPENAI_API_KEY or ANTHROPIC_API_KEY) condition. That means you may end up sending "ollama-local" as an OpenAI/Claude key, which is misleading and makes misconfiguration harder to diagnose.

Safer fallback pattern
-        if provider == "ollama" or not (
-            os.environ.get("OPENAI_API_KEY") or os.environ.get("ANTHROPIC_API_KEY")
-        ):
-            return "ollama-local"
+        if provider == "ollama":
+            # Local provider: no external API key needed
+            return "ollama-local"
+
+        # For explicit non-ollama providers with no valid key, fail fast
+        if not (
+            os.environ.get("OPENAI_API_KEY") or os.environ.get("ANTHROPIC_API_KEY")
+        ):
+            self._print_error("No API keys configured. Set OPENAI_API_KEY/ANTHROPIC_API_KEY or use CORTEX_PROVIDER=ollama.")
+            return None

This keeps the Ollama fallback for the Ollama provider, while making misconfigurations explicit instead of silently substituting an unrelated key.

examples/env_demo.py (1)

1-2: Fix shebang / __future__ order to satisfy Python and Ruff.

Shebang must be the very first line; move from __future__ import annotations below the module docstring (before other imports) to fix the I001 error and keep the file executable.

Proposed reorder
-from __future__ import annotations
-#!/usr/bin/env python3
-"""
-Environment Variable Manager Demo
+#!/usr/bin/env python3
+"""
+Environment Variable Manager Demo
@@
-Run this script to see the env manager in action:
-    python examples/env_demo.py
-"""
-
-import os
+Run this script to see the env manager in action:
+    python examples/env_demo.py
+"""
+
+from __future__ import annotations
+
+import os
tests/test_cli.py (1)

66-78: Fix mixed mocking style and incorrect monkeypatch usage in test_install_dry_run.

This test still mixes unittest-style @patch decorators with a monkeypatch parameter (which isn’t injected in unittest.TestCase), and double-patches CommandInterpreter. The current signature will receive the mock from the decorator as the monkeypatch argument, so monkeypatch.setenv will fail.

Proposed pure-unittest fix
-    @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-openai-key-123"}, clear=True)
-    @patch("cortex.cli.CommandInterpreter")
-    def test_install_dry_run(self, monkeypatch):
-        monkeypatch.setenv("OPENAI_API_KEY", "sk-test-openai-key-123")
-        with patch("cortex.cli.CommandInterpreter") as mock_interpreter_class:
-            mock_interpreter = Mock()
-            mock_interpreter.parse.return_value = ["apt update", "apt install docker"]
-            mock_interpreter_class.return_value = mock_interpreter
-
-            result = self.cli.install("docker", dry_run=True)
-
-            self.assertEqual(result, 0)
-            mock_interpreter.parse.assert_called_once_with("install docker")
+    @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-openai-key-123"}, clear=True)
+    @patch("cortex.cli.CommandInterpreter")
+    def test_install_dry_run(self, mock_interpreter_class):
+        mock_interpreter = Mock()
+        mock_interpreter.parse.return_value = ["apt update", "apt install docker"]
+        mock_interpreter_class.return_value = mock_interpreter
+
+        result = self.cli.install("docker", dry_run=True)
+
+        self.assertEqual(result, 0)
+        mock_interpreter.parse.assert_called_once_with("install docker")
🧹 Nitpick comments (2)
tests/test_cli_extended.py (1)

33-37: Confirm intended semantics for installs without API keys across test suites.

These changes align extended tests with the new behavior (_get_api_key falling back to "ollama-local" and install() succeeding when using the local provider), and the tighter assert_called_once_with("install docker") matches the updated CLI implementation.

However, tests/test_cli.TestCortexCLI.test_install_no_api_key still expects install("docker") to return 1 when no API key is set. That contradicts the behavior being asserted here (return 0 via ollama fallback).

You may want to:

  • Decide whether “no API key” should succeed via Ollama or fail fast, and
  • Align both tests/test_cli.py and tests/test_cli_extended.py with that single, documented behavior.

Also applies to: 76-84, 106-125

cortex/cli.py (1)

331-403: Provider probing loop in install() looks good, but unused api_key and broad exception mask could be clarified.

The multi-provider probing (providers_to_try) and loop over CommandInterpreter look consistent with the tests and correctly short-circuit on the first provider that yields commands, falling back when exceptions occur.

Two nits:

  • api_key is assigned in the loop but never used afterwards; it can be dropped unless you plan to surface it later (e.g., for telemetry).
  • Catching (RuntimeError, Exception) is effectively the same as catching Exception; if you don’t need special handling for RuntimeError, consider just except Exception as e: to make intent clearer.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8ac7e5 and 82595c8.

📒 Files selected for processing (13)
  • .env.example
  • cortex/cli.py
  • cortex/env_loader.py
  • cortex/first_run_wizard.py
  • cortex/utils/api_key_validator.py
  • examples/env_demo.py
  • examples/parallel_llm_demo.py
  • test_parallel_llm.py
  • tests/installer/test_parallel_install.py
  • tests/test_cli.py
  • tests/test_cli_extended.py
  • tests/test_env_manager.py
  • tests/test_first_run_wizard.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • cortex/utils/api_key_validator.py
  • tests/test_env_manager.py
  • cortex/env_loader.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • tests/test_cli.py
  • tests/installer/test_parallel_install.py
  • tests/test_cli_extended.py
  • test_parallel_llm.py
  • examples/env_demo.py
  • cortex/first_run_wizard.py
  • cortex/cli.py
  • examples/parallel_llm_demo.py
  • tests/test_first_run_wizard.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_cli.py
  • tests/installer/test_parallel_install.py
  • tests/test_cli_extended.py
  • tests/test_first_run_wizard.py
**/*install*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*install*.py: Dry-run by default for all installations in command execution
No silent sudo execution - require explicit user confirmation
Implement audit logging to ~/.cortex/history.db for all package operations

Files:

  • tests/installer/test_parallel_install.py
🧠 Learnings (2)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*install*.py : Dry-run by default for all installations in command execution

Applied to files:

  • tests/test_cli.py
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*.py : Docstrings required for all public APIs

Applied to files:

  • cortex/first_run_wizard.py
🧬 Code graph analysis (5)
tests/test_cli_extended.py (2)
tests/test_cli.py (2)
  • test_get_api_key_not_found (31-34)
  • test_install_no_api_key (62-64)
cortex/cli.py (2)
  • _get_api_key (57-72)
  • install (331-594)
test_parallel_llm.py (1)
cortex/llm_router.py (8)
  • LLMRouter (76-846)
  • TaskType (32-42)
  • check_hardware_configs_parallel (979-1027)
  • diagnose_errors_parallel (930-976)
  • query_multiple_packages (880-927)
  • acomplete (543-609)
  • complete_batch (765-846)
  • set_rate_limit (534-541)
cortex/first_run_wizard.py (1)
cortex/utils/api_key_validator.py (2)
  • validate_anthropic_api_key (10-28)
  • validate_openai_api_key (34-48)
examples/parallel_llm_demo.py (1)
cortex/llm_router.py (1)
  • query_multiple_packages (880-927)
tests/test_first_run_wizard.py (1)
cortex/first_run_wizard.py (1)
  • _step_api_setup (640-701)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 1-1: [KeyWithoutValue] The <<<<<<< HEAD key should be with a value or have an equal sign

(KeyWithoutValue)


[warning] 1-1: [LeadingCharacter] Invalid leading character detected

(LeadingCharacter)


[warning] 63-63: [LeadingCharacter] Invalid leading character detected

(LeadingCharacter)


[warning] 68-68: [UnorderedKey] The ANTHROPIC_API_KEY key should go before the OPENAI_API_KEY key

(UnorderedKey)


[warning] 69-69: [IncorrectDelimiter] The >>>>>>> 34c66df (feat: seamless onboarding—auto-create .env from .env.example, improved wizard flow) key has incorrect delimiter

(IncorrectDelimiter)


[warning] 69-69: [KeyWithoutValue] The >>>>>>> 34c66df (feat: seamless onboarding—auto-create .env from .env.example, improved wizard flow) key should be with a value or have an equal sign

(KeyWithoutValue)


[warning] 69-69: [LeadingCharacter] Invalid leading character detected

(LeadingCharacter)


[warning] 69-69: [LowercaseKey] The >>>>>>> 34c66df (feat: seamless onboarding—auto-create .env from .env.example, improved wizard flow) key should be in uppercase

(LowercaseKey)


[warning] 69-69: [UnorderedKey] The >>>>>>> 34c66df (feat: seamless onboarding—auto-create .env from .env.example, improved wizard flow) key should go before the ANTHROPIC_API_KEY key

(UnorderedKey)

🪛 GitHub Actions: CI
examples/env_demo.py

[error] 1-1: I001 Import block is un-sorted or un-formatted.

🪛 GitHub Check: lint
examples/env_demo.py

[failure] 1-1: Ruff (I001)
examples/env_demo.py:1:1: I001 Import block is un-sorted or un-formatted

examples/parallel_llm_demo.py

[failure] 218-218: Ruff (W293)
examples/parallel_llm_demo.py:218:1: W293 Blank line contains whitespace


[failure] 216-216: Ruff (W293)
examples/parallel_llm_demo.py:216:1: W293 Blank line contains whitespace

🪛 GitHub Check: Lint
examples/env_demo.py

[failure] 1-1: Ruff (I001)
examples/env_demo.py:1:1: I001 Import block is un-sorted or un-formatted

examples/parallel_llm_demo.py

[failure] 218-218: Ruff (W293)
examples/parallel_llm_demo.py:218:1: W293 Blank line contains whitespace


[failure] 216-216: Ruff (W293)
examples/parallel_llm_demo.py:216:1: W293 Blank line contains whitespace

🪛 Gitleaks (8.30.0)
tests/test_first_run_wizard.py

[high] 269-269: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
🔇 Additional comments (3)
cortex/cli.py (1)

738-779: env subcommand handler is structurally solid.

The env() method cleanly routes subcommands to dedicated helpers and wraps the entire operation in a single try/except, producing a user-friendly error on unexpected failures. Type hints and docstrings on the helpers follow the coding guidelines.

cortex/first_run_wizard.py (2)

318-352: Guard _prompt_for_api_key against non-interactive mode to avoid infinite loops.

_prompt_for_api_key() loops until a non-blank, correctly prefixed key is entered. When self.interactive is False, _prompt() immediately returns the default (empty string), so this loop will never make progress if run() is ever invoked in non-interactive mode.

[ suggest_recommended_refactor ]

Suggested non-interactive guard
     def _prompt_for_api_key(self, key_type: str) -> str | None:
         """Prompt user for a valid API key, rejecting blank inputs."""
+        if not self.interactive:
+            # In non-interactive mode we cannot prompt; let caller handle None.
+            return None
@@
-        while True:
+        while True:
             key = self._prompt(f"Enter your {provider_name} API key (or 'q' to cancel): ")

This keeps current interactive behavior but ensures non-interactive flows fail fast instead of hanging.


463-562: New provider selection + dry-run verification flow looks solid.

The main provider-selection branch in run():

  • Correctly distinguishes Anthropic/OpenAI/Ollama and a “skip reconfiguration” path.
  • Reuses existing keys when present, only prompting to replace them when requested.
  • Saves keys preferentially to .env with a shell-profile fallback.
  • Sets self.config["api_provider"]/["api_key_configured"] consistently.
  • Verifies the chosen provider via a cortex install "<example>" dry run with the appropriate forced_provider argument.

Assuming the ENV-handling fixes above, this flow nicely satisfies the wizard UX goals.

Comment on lines +146 to +176
def get_valid_api_key(env_var: str, key_type: str = "generic") -> str | None:
"""
Get a valid API key from .env file first, then environment variable.
Treats blank keys as missing.
"""
key_from_file = read_key_from_env_file(env_var)

env_path = get_env_file_path()
logger.debug(f"Checking {env_var} in {env_path}: '{key_from_file}'")

if key_from_file is not None and len(key_from_file) > 0:
if is_valid_api_key(key_from_file, key_type):
os.environ[env_var] = key_from_file
return key_from_file
return None

if env_var in os.environ:
del os.environ[env_var]

return None


def detect_available_providers() -> list[str]:
"""Detect available providers based on valid (non-blank) API keys in .env file."""
providers = []

if get_valid_api_key("ANTHROPIC_API_KEY", "anthropic"):
providers.append("anthropic")
if get_valid_api_key("OPENAI_API_KEY", "openai"):
providers.append("openai")
if shutil.which("ollama"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

ENV-exported API keys are still deleted/ignored, so the wizard doesn’t honor existing keys.

The combination of get_valid_api_key() and the early loop in run() still removes shell-exported API keys instead of using them:

  • get_valid_api_key() only checks the .env file; when no valid entry is found it does:

    if env_var in os.environ:
        del os.environ[env_var]
    return None
  • run() then calls read_key_from_env_file() and, if the file has no value, again deletes OPENAI_API_KEY/ANTHROPIC_API_KEY from os.environ.

This directly contradicts the PR objective: a user who has only export OPENAI_API_KEY=... (no .env) will have that key removed from the process environment as soon as cortex wizard runs, and detect_available_providers() will never see it.

Suggested fix: honor ENV first and stop deleting valid shell keys
  1. Make get_valid_api_key() prefer .env but fall back to os.environ without deleting valid ENV-only keys:
 def get_valid_api_key(env_var: str, key_type: str = "generic") -> str | None:
@@
-    if key_from_file is not None and len(key_from_file) > 0:
-        if is_valid_api_key(key_from_file, key_type):
-            os.environ[env_var] = key_from_file
-            return key_from_file
-        return None
-
-    if env_var in os.environ:
-        del os.environ[env_var]
-
-    return None
+    # Prefer .env if it contains a valid, non-blank key
+    if key_from_file and is_valid_api_key(key_from_file, key_type):
+        os.environ[env_var] = key_from_file
+        return key_from_file
+
+    # Fall back to existing environment value
+    env_value = os.environ.get(env_var)
+    if env_value and is_valid_api_key(env_value, key_type):
+        return env_value
+
+    # Neither source has a valid key; optionally clear an invalid env var
+    os.environ.pop(env_var, None)
+    return None
  1. Remove the explicit deletion loop in run(); let load_dotenv() + get_valid_api_key() handle precedence:
-        for key_name in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"]:
-            file_value = read_key_from_env_file(key_name)
-            if file_value is None or len(file_value.strip()) == 0:
-                if key_name in os.environ:
-                    del os.environ[key_name]
-
-        available_providers = detect_available_providers()
+        # Do not delete shell-exported keys; detect_available_providers()
+        # will consult both .env and os.environ via get_valid_api_key().
+        available_providers = detect_available_providers()

With these changes, a user who has only exported an API key in their shell will see that provider listed as available and can proceed through the wizard without re-entering the key, satisfying issue #355.

Also applies to: 392-407

🤖 Prompt for AI Agents
In cortex/first_run_wizard.py around lines 146-176 (and also apply to logic at
392-407): update get_valid_api_key() so it checks the .env value first but if
that value is None or blank, fall back to checking os.environ for the key
(validate that value with is_valid_api_key) and do NOT delete os.environ
entries; remove the block that deletes env vars in run() (lines ~392-407) so we
no longer remove shell-exported API keys and instead let
load_dotenv()+get_valid_api_key() decide precedence.

Comment on lines 18 to +22
# Create 3 independent commands using Python's time.sleep (Windows-compatible)
commands = [
"python -c \"import time; time.sleep(0.1); print('Task 1')\"",
"python -c \"import time; time.sleep(0.1); print('Task 2')\"",
"python -c \"import time; time.sleep(0.1); print('Task 3')\"",
"python3 -c \"import time; time.sleep(0.1); print('Task 1')\"",
"python3 -c \"import time; time.sleep(0.1); print('Task 2')\"",
"python3 -c \"import time; time.sleep(0.1); print('Task 3')\"",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, check if the test file exists and view the relevant lines
if [ -f "tests/installer/test_parallel_install.py" ]; then
  echo "=== File content (lines 15-25) ==="
  sed -n '15,25p' tests/installer/test_parallel_install.py
  echo ""
  echo "=== All instances of python/python3 in this file ==="
  grep -n "python" tests/installer/test_parallel_install.py | head -20
else
  echo "File not found, checking for installer test files:"
  find tests -name "*parallel*" -o -name "*install*" 2>/dev/null
fi

Repository: cortexlinux/cortex

Length of output: 148


🏁 Script executed:

# Check for CI configuration files that specify Windows testing
echo "=== Checking for CI/CD configurations ==="
find .github -name "*.yml" -o -name "*.yaml" 2>/dev/null | while read file; do
  echo "File: $file"
  grep -i "windows\|os:" "$file" | head -10
done

Repository: cortexlinux/cortex

Length of output: 268


🏁 Script executed:

# Check for other test files and their python command patterns
echo "=== Pattern of python commands in other test files ==="
rg -t py "python[0-9]? -c|python -c" tests/ --max-count=5 -A 1

Repository: cortexlinux/cortex

Length of output: 148


🏁 Script executed:

# Check project documentation for platform support
echo "=== Checking README and docs for platform mentions ==="
find . -maxdepth 2 -name "README*" -o -name "CONTRIBUTING*" 2>/dev/null | while read file; do
  [ -f "$file" ] && echo "=== $file ===" && head -50 "$file" | grep -i "python\|windows\|platform" || true
done

Repository: cortexlinux/cortex

Length of output: 273


🏁 Script executed:

# Read the specific test file - just the relevant section
cat tests/installer/test_parallel_install.py 2>/dev/null | head -50

Repository: cortexlinux/cortex

Length of output: 1888


🏁 Script executed:

# Check CI file for Windows testing
grep -i "windows\|ubuntu\|macos\|runs-on" .github/workflows/ci.yml 2>/dev/null

Repository: cortexlinux/cortex

Length of output: 169


🏁 Script executed:

# Quick search for python3 vs python in test file
grep -c "python3 -c" tests/installer/test_parallel_install.py 2>/dev/null
grep -c "python -c" tests/installer/test_parallel_install.py 2>/dev/null

Repository: cortexlinux/cortex

Length of output: 148


🏁 Script executed:

# Check if there are any Windows-specific test markers or skips
grep -r "windows\|platform\|skipif" tests/installer/ 2>/dev/null | grep -v ".pyc"

Repository: cortexlinux/cortex

Length of output: 44


🏁 Script executed:

# Check setup.py, pyproject.toml, or similar for platform info
cat pyproject.toml 2>/dev/null | grep -A 10 -i "python\|platform" | head -30

Repository: cortexlinux/cortex

Length of output: 801


🏁 Script executed:

# Look for other command execution patterns in the codebase
grep -r "subprocess\|Popen\|run\|shell=True" tests/installer/ 2>/dev/null | head -10

Repository: cortexlinux/cortex

Length of output: 964


🏁 Script executed:

# Verify the exact OS declaration in pyproject.toml
grep -A 2 "Operating System" pyproject.toml

Repository: cortexlinux/cortex

Length of output: 128


Remove or clarify the misleading "Windows-compatible" comment.

The project explicitly declares "Operating System :: POSIX :: Linux" in pyproject.toml and runs tests only on Ubuntu. The python3 command is appropriate for Linux environments with Python 3.10+. The comment on line 18 stating "Windows-compatible" is misleading since Windows support is not in scope for this project. Update the comment to accurately reflect the Linux-only requirement (e.g., "Create 3 independent commands using Python's time.sleep") or remove it entirely.

Also applies to: 45-47, 73-75, 101-104, 124-124, 141-141, 154-154, 180-180, 200-202, 221-221, 250-253, 279-281

🤖 Prompt for AI Agents
In tests/installer/test_parallel_install.py around lines 18-22, the inline
comment saying "Windows-compatible" is misleading for a Linux-only project;
remove that phrase or replace the comment with an accurate statement such as
"Create 3 independent commands using Python's time.sleep" (or simply "Create 3
independent commands using Python's time.sleep") and apply the same change to
the other occurrences listed (lines 45-47, 73-75, 101-104, 124, 141, 154, 180,
200-202, 221, 250-253, 279-281) so that comments do not claim Windows
compatibility.

Comment on lines +261 to +276
@patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test-key-12345678"})
def test_step_api_setup_existing_anthropic_key(self, wizard):
"""Test API setup with existing Anthropic key."""
result = wizard._step_api_setup()

assert result.success is True
assert wizard.config.get("api_provider") == "anthropic"

@patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-key-12345678"}, clear=True)
def test_step_api_setup_existing_openai_key(self, wizard):
"""Test API setup with existing OpenAI key."""
result = wizard._step_api_setup()

assert result.success is True
assert wizard.config.get("api_provider") == "openai"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Replace realistic-looking API key placeholders to satisfy Gitleaks.

The hard-coded values "sk-ant-test-key-12345678" and "sk-test-key-12345678" resemble real API keys and trigger Gitleaks. The tests only need non-empty values, so you can use safer placeholders that don’t match secret patterns.

Suggested safer test values
-    @patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test-key-12345678"})
+    @patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-anthropic-api-key"})
@@
-    @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-key-12345678"}, clear=True)
+    @patch.dict(os.environ, {"OPENAI_API_KEY": "test-openai-api-key"}, clear=True)

These still make _step_api_setup treat the keys as “present” without tripping secret scanners.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test-key-12345678"})
def test_step_api_setup_existing_anthropic_key(self, wizard):
"""Test API setup with existing Anthropic key."""
result = wizard._step_api_setup()
assert result.success is True
assert wizard.config.get("api_provider") == "anthropic"
@patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-key-12345678"}, clear=True)
def test_step_api_setup_existing_openai_key(self, wizard):
"""Test API setup with existing OpenAI key."""
result = wizard._step_api_setup()
assert result.success is True
assert wizard.config.get("api_provider") == "openai"
@patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-anthropic-api-key"})
def test_step_api_setup_existing_anthropic_key(self, wizard):
"""Test API setup with existing Anthropic key."""
result = wizard._step_api_setup()
assert result.success is True
assert wizard.config.get("api_provider") == "anthropic"
@patch.dict(os.environ, {"OPENAI_API_KEY": "test-openai-api-key"}, clear=True)
def test_step_api_setup_existing_openai_key(self, wizard):
"""Test API setup with existing OpenAI key."""
result = wizard._step_api_setup()
assert result.success is True
assert wizard.config.get("api_provider") == "openai"
🧰 Tools
🪛 Gitleaks (8.30.0)

[high] 269-269: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
In tests/test_first_run_wizard.py around lines 261 to 276, the hard-coded API
key strings ("sk-ant-test-key-12345678" and "sk-test-key-12345678") look like
real secrets and trigger Gitleaks; replace them with non-secret placeholders
that are non-empty but do not match secret/key patterns (e.g.,
"ANTHROPIC_KEY_PRESENT" and "OPENAI_KEY_PRESENT" or similar), keeping the
patch.dict usage and test logic unchanged so _step_api_setup still detects a
present key.

@sujay-d07
Copy link
Collaborator

@Suyashd999 This PR fulfills the requirements of Issue #356, once the Lint Formatting Issues are solved, its ready to merge.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
3 Security Hotspots
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
examples/env_demo.py (1)

1-2: [DUPLICATE] Fix import ordering: shebang must be first.

The from __future__ import annotations import is placed before the shebang, violating Python syntax and PEP 8. This issue was already flagged in a previous review.

examples/parallel_llm_demo.py (2)

39-40: [DUPLICATE] Remove duplicate API call that doubles costs.

Line 39 calls query_multiple_packages but the result is immediately overwritten by the identical call on line 40. This issue was already flagged in a previous review.


216-218: [DUPLICATE] Remove trailing whitespace to pass linter.

Lines 216 and 218 contain trailing whitespace causing W293 linter failures. This was already flagged in a previous review.

🧹 Nitpick comments (3)
cortex/dependency_check.py (1)

26-67: Consider adding docstrings to public functions.

Per the coding guidelines, docstrings are required for all public APIs. The following functions would benefit from docstrings describing their purpose, parameters, and return values:

  • check_dependency (lines 26-31)
  • get_missing_dependencies (lines 34-35)
  • format_installation_instructions (lines 38-60)
  • verify_dependencies_or_exit (lines 63-67)

This is a pre-existing gap across the file and can be addressed in a follow-up refactoring.

cortex/utils/api_key_validator.py (2)

10-28: Enhance docstring per coding guidelines.

The function lacks detailed parameter and return documentation required by the coding guidelines for public APIs.

🔎 Proposed docstring enhancement
 def validate_anthropic_api_key(api_key: str) -> bool:
-    """Validate Anthropic (Claude) API key by making a minimal request."""
+    """Validate Anthropic (Claude) API key by making a minimal request.
+    
+    Makes a minimal POST request to the Anthropic API to verify the key works.
+    
+    Args:
+        api_key: The Anthropic API key to validate (should start with 'sk-ant-')
+    
+    Returns:
+        True if the API key is valid (HTTP 200 response), False otherwise.
+    """
     try:

Based on coding guidelines.


31-45: Enhance docstring per coding guidelines.

The function lacks detailed parameter and return documentation required by the coding guidelines for public APIs.

🔎 Proposed docstring enhancement
 def validate_openai_api_key(api_key: str) -> bool:
-    """Validate OpenAI API key by making a minimal request."""
+    """Validate OpenAI API key by making a minimal request.
+    
+    Makes a minimal POST request to the OpenAI chat completions API to verify the key works.
+    
+    Args:
+        api_key: The OpenAI API key to validate (should start with 'sk-')
+    
+    Returns:
+        True if the API key is valid (HTTP 200 response), False otherwise.
+    """
     try:

Based on coding guidelines.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82595c8 and 14cf97a.

📒 Files selected for processing (6)
  • cortex/dependency_check.py
  • cortex/first_run_wizard.py
  • cortex/llm/interpreter.py
  • cortex/utils/api_key_validator.py
  • examples/env_demo.py
  • examples/parallel_llm_demo.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/llm/interpreter.py
  • examples/env_demo.py
  • cortex/first_run_wizard.py
  • examples/parallel_llm_demo.py
  • cortex/dependency_check.py
  • cortex/utils/api_key_validator.py
🧠 Learnings (1)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*.py : Docstrings required for all public APIs

Applied to files:

  • cortex/first_run_wizard.py
🧬 Code graph analysis (2)
cortex/first_run_wizard.py (1)
cortex/utils/api_key_validator.py (2)
  • validate_anthropic_api_key (10-28)
  • validate_openai_api_key (31-45)
examples/parallel_llm_demo.py (1)
cortex/llm_router.py (1)
  • query_multiple_packages (880-927)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.12)
  • GitHub Check: Test (Python 3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (2)
cortex/dependency_check.py (1)

46-46: LGTM - Formatting improvement.

The change to use ", ".join(missing) produces clean, comma-separated package names in the user-facing error message, improving readability.

cortex/llm/interpreter.py (1)

118-119: LGTM! Clean formatting adjustment.

The Ollama client initialization has been reformatted to place api_key and base_url on separate lines. This improves readability with no functional changes.

Comment on lines +21 to +22
# Import API key validation utilities
from cortex.utils.api_key_validator import validate_anthropic_api_key, validate_openai_api_key
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused imports.

The imported validator functions validate_anthropic_api_key and validate_openai_api_key are never called in this file. Either remove these unused imports or integrate the validators into the API key verification flow.

🤖 Prompt for AI Agents
In cortex/first_run_wizard.py around lines 21 to 22, the imports
validate_anthropic_api_key and validate_openai_api_key are unused; remove these
two imports from the file (or alternatively, if API key validation is desired
here, call the appropriate validator(s) in the key verification flow and
handle/report validation errors). Ensure no other code references these names
and run linting/tests to confirm the unused-import error is resolved.

Comment on lines +146 to +165
def get_valid_api_key(env_var: str, key_type: str = "generic") -> str | None:
"""
Get a valid API key from .env file first, then environment variable.
Treats blank keys as missing.
"""
key_from_file = read_key_from_env_file(env_var)

env_path = get_env_file_path()
logger.debug(f"Checking {env_var} in {env_path}: '{key_from_file}'")

if key_from_file is not None and len(key_from_file) > 0:
if is_valid_api_key(key_from_file, key_type):
os.environ[env_var] = key_from_file
return key_from_file
return None

if env_var in os.environ:
del os.environ[env_var]

return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CRITICAL: Function deletes shell-exported API keys, contradicting PR objectives.

This function claims to check "from .env file first, then environment variable" (line 148) but never checks os.environ for existing keys. Lines 162-163 explicitly delete any environment-exported API key when the .env file doesn't contain a valid value:

if env_var in os.environ:
    del os.environ[env_var]  # ← Deletes shell-exported keys!

This is the exact bug the PR is supposed to fix (issue #355: wizard exits when API key is exported in shell). Users who export OPENAI_API_KEY=... in their shell will have their keys deleted and the wizard will fail.

🔎 Proposed fix to honor environment-exported keys
 def get_valid_api_key(env_var: str, key_type: str = "generic") -> str | None:
     """
     Get a valid API key from .env file first, then environment variable.
     Treats blank keys as missing.
     """
     key_from_file = read_key_from_env_file(env_var)
 
     env_path = get_env_file_path()
     logger.debug(f"Checking {env_var} in {env_path}: '{key_from_file}'")
 
     if key_from_file is not None and len(key_from_file) > 0:
         if is_valid_api_key(key_from_file, key_type):
             os.environ[env_var] = key_from_file
             return key_from_file
-        return None
+        # .env file has invalid key, fall through to check os.environ
 
+    # Check environment variable as fallback
+    env_value = os.environ.get(env_var)
+    if env_value and is_valid_api_key(env_value.strip(), key_type):
+        return env_value.strip()
+
+    # No valid key in either location
     if env_var in os.environ:
         del os.environ[env_var]
 
     return None
🤖 Prompt for AI Agents
In cortex/first_run_wizard.py around lines 146-165, the function currently
removes shell-exported API keys instead of honoring them; change the logic so
that if the key from the .env file is missing or blank the function next checks
os.environ for env_var, and if that env value exists and is non-empty and passes
is_valid_api_key(key, key_type) return it (do not delete it); only overwrite
os.environ when you have a valid key_from_file, and do not delete an existing
environment-exported key when the .env file is empty or invalid—log the outcome
instead.

Comment on lines +398 to +402
for key_name in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"]:
file_value = read_key_from_env_file(key_name)
if file_value is None or len(file_value.strip()) == 0:
if key_name in os.environ:
del os.environ[key_name]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CRITICAL: Code explicitly deletes shell-exported API keys.

This loop unconditionally deletes OPENAI_API_KEY and ANTHROPIC_API_KEY from os.environ if they're not present in the .env file, even when users have exported them in their shell:

for key_name in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"]:
    file_value = read_key_from_env_file(key_name)
    if file_value is None or len(file_value.strip()) == 0:
        if key_name in os.environ:
            del os.environ[key_name]  # ← Breaks shell-exported keys!

This directly contradicts the PR objective: "allow setup to continue when API key is already present in ENV." Remove this deletion logic and let detect_available_providers() discover keys from either source.

🔎 Proposed fix
-    for key_name in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"]:
-        file_value = read_key_from_env_file(key_name)
-        if file_value is None or len(file_value.strip()) == 0:
-            if key_name in os.environ:
-                del os.environ[key_name]
-
     available_providers = detect_available_providers()

After fixing get_valid_api_key() to check os.environ, remove this deletion block entirely.

🤖 Prompt for AI Agents
In cortex/first_run_wizard.py around lines 398 to 402, the loop removes
OPENAI_API_KEY and ANTHROPIC_API_KEY from os.environ when the keys are missing
or empty in the .env file; this deletes shell-exported keys and contradicts the
intended behavior. Remove this deletion block entirely so environment variables
are left intact and ensure get_valid_api_key() is adjusted to check os.environ
first (falling back to .env) so detect_available_providers() can find keys from
either source.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Cortex wizard stops even when API key is already set in ENV

2 participants