Skip to content

Conversation

@jaysurse
Copy link
Collaborator

@jaysurse jaysurse commented Dec 24, 2025

Closes #547

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

    • Enhanced interactive first-run wizard with provider selection for OpenAI, Anthropic, and Ollama
    • Improved API key detection, validation, and storage with fallback support
    • Multi-provider support enabling automatic fallback when primary provider unavailable
    • Environment-based configuration with flexible .env file loading
  • Bug Fixes

    • Improved error handling and user messaging throughout setup flow
    • Better validation of API keys before use
    • Enhanced fallback mechanism when API keys are unavailable

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 24, 2025

Warning

Rate limit exceeded

@Anshgrover23 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 22 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between c5ce259 and 37d20a5.

📒 Files selected for processing (2)
  • cortex/cli.py
  • tests/test_cli.py

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

This PR introduces provider-aware API key management and a comprehensive first-run wizard for interactive setup and reconfiguration. Core enhancements include provider-scoped key retrieval in the CLI, multi-provider installation flow with fallback logic, a refactored first-run wizard with environment persistence, new API key validation utilities, and expanded environment loader search paths. Test coverage is substantially extended across multiple modules, and demo files are updated with new async/parallel testing infrastructure.

Changes

Cohort / File(s) Summary
Core CLI Enhancements
cortex/cli.py
Added _get_api_key_for_provider(provider) for provider-scoped key retrieval; reworked _get_api_key() for provider-aware resolution with fallback to ollama-local; enhanced install() to probe multiple providers and handle per-provider API keys; added notify(args) public method for desktop notifications; expanded env(args) with broader exception handling; wizard() now delegates to FirstRunWizard(interactive=True); added import of FirstRunWizard.
First-Run Wizard Refactor
cortex/first_run_wizard.py
Substantial overhaul: added environment-based API key utilities (get_env_file_path, read_key_from_env_file, save_key_to_env_file, is_valid_api_key, get_valid_api_key, detect_available_providers); integrated provider detection via api_key_detector; expanded public API surface with WizardState, WizardStep, StepResult, needs_first_run, run_wizard exports; implemented interactive provider selection UI with dry-run verification; added shell-integrated and encrypted storage fallbacks for API keys.
Environment & API Key Management
cortex/env_loader.py, cortex/utils/api_key_validator.py, cortex/api_key_detector.py
env_loader: extended .env search to include parent directory and cortex package directory with graceful ImportError handling. api_key_validator: new module with validate_anthropic_api_key() and validate_openai_api_key() functions performing minimal API calls with timeout. api_key_detector: added backward-compat APIs detect_api_key(provider) and get_detected_provider(); introduced SUPPORTED_PROVIDERS constant.
Test Updates
tests/test_cli.py, tests/test_cli_extended.py, tests/test_first_run_wizard.py, tests/test_parallel_install.py, tests/test_env_manager.py, tests/test_api_key_detector.py
test_cli: renamed/repurposed no-API-key test to verify Ollama fallback; added dry-run test with mocked CommandInterpreter. test_cli_extended: shifted to environment-driven configuration with OPENAI_API_KEY/CORTEX_PROVIDER; replaced _get_api_key mocks with _get_api_key_for_provider. test_first_run_wizard: added TestGetValidApiKey scenarios for .env/shell key loading and priority; expanded API setup step tests for Anthropic/OpenAI; removed integration test. test_parallel_install: upgraded python invocations to python3. test_env_manager: replaced sftp with ftps in URL validation. test_api_key_detector: patched both Path.home and Path.cwd in no-key scenario.
Examples & New Test Suite
examples/env_demo.py, examples/parallel_llm_demo.py, test_parallel_llm.py
env_demo: moved __future__ import to file top; removed duplicate. parallel_llm_demo: added duplicate await in demo_multi_package_queries (second result overwrites); added formatting blank lines. test_parallel_llm: new comprehensive async test suite with test_async_completion, test_batch_processing, test_rate_limiting, test_helper_functions, test_performance_comparison, and main() orchestrator.
Configuration
.env.example
Added example API keys (OPENAI_API_KEY, ANTHROPIC_API_KEY) with merge conflict markers indicating unresolved merge state.

Sequence Diagrams

sequenceDiagram
    participant User
    participant CLI as CortexCLI
    participant Detector as api_key_detector
    participant Wizard as FirstRunWizard
    participant Storage as Env/Storage

    User->>CLI: install(software, forced_provider=None)
    CLI->>CLI: Detect initial_provider
    CLI->>Detector: detect() for available providers
    Detector-->>CLI: provider list + keys
    CLI->>CLI: Build providers_to_try (primary + secondary if both have keys)
    
    loop For each provider in providers_to_try
        CLI->>CLI: _get_api_key_for_provider(provider)
        CLI->>Detector: detect_api_key(provider)
        Detector-->>CLI: api_key or None
        alt Key Found
            CLI->>CLI: Build CommandInterpreter with key
            CLI->>CLI: Execute install command
            CLI-->>User: Success
        else No Key
            CLI->>CLI: Try next provider
        end
    end
    
    alt No providers succeeded
        CLI-->>User: Error: No valid provider found
    end
Loading
sequenceDiagram
    participant User
    participant CLI as CortexCLI
    participant Wizard as FirstRunWizard
    participant Validator as api_key_validator
    participant Storage as Env/Storage

    User->>CLI: wizard()
    CLI->>Wizard: FirstRunWizard(interactive=True)
    Wizard->>Wizard: Show welcome banner
    Wizard->>Wizard: detect_available_providers()
    Wizard->>User: Present provider options (Anthropic, OpenAI, Ollama)
    
    User->>Wizard: Select provider
    Wizard->>Storage: read_key_from_env_file()
    alt Existing key found
        Wizard->>Validator: validate_anthropic_api_key() or validate_openai_api_key()
        Validator-->>Wizard: Valid/Invalid
        Wizard->>User: Confirm use or replace
    else No existing key
        Wizard->>User: Prompt for API key
        User->>Wizard: Enter key
    end
    
    Wizard->>Storage: save_key_to_env_file() or shell config or encrypted storage
    Wizard->>Wizard: Run dry-run verification
    Wizard-->>User: Setup complete / Success
    Wizard-->>CLI: Return 0
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Suggested labels

MVP

Suggested reviewers

  • mikejmorgan-ai
  • Suyashd999
  • Anshgrover23

Poem

🐰 A wizard hops through providers three,
Keys dancing in harmony,
First-run flows with interactive grace,
Fallbacks guide each user's pace,
Cortex learns where to roam!

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Most changes directly support the wizard API key detection feature. However, several changes appear tangential: test_parallel_llm.py (new integration test unrelated to wizard), parallel_llm_demo.py formatting/redundant await, examples/env_demo.py import reordering, and .env.example merge conflict markers are out of scope. Remove or defer out-of-scope changes: test_parallel_llm.py, redundant await in parallel_llm_demo.py, and resolve .env.example merge conflict. Keep only wizard-related and supporting infrastructure changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 77.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main fix: allowing the wizard to continue when an API key already exists in the environment, which is the core issue addressed.
Description check ✅ Passed The description provides a summary of what was fixed and links to the related issue #547, though it lacks information about AI tool usage and test validation completion.
Linked Issues check ✅ Passed Changes implement the core requirements from #547: detecting existing environment API keys [cortex/first_run_wizard.py, cortex/cli.py], preventing premature wizard exit [cortex/cli.py wizard flow], and enabling provider confirmation through interactive reconfiguration [cortex/first_run_wizard.py expanded UI].

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

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)

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 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.

requirements.txt Outdated
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 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
Copy link
Collaborator

@Anshgrover23 Anshgrover23 left a comment

Choose a reason for hiding this comment

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

@jaysurse Kindle Updated your branch and merge with main,.

@@ -1,3 +1,4 @@
<<<<<<< HEAD
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove this comments.


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)
Copy link
Collaborator

Choose a reason for hiding this comment

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

remove this one as well.

Resolved conflicts in:
- cortex/cli.py: kept improved _get_api_key_for_provider method
- tests/test_cli_extended.py: kept simplified test matching new implementation
Copilot AI review requested due to automatic review settings January 5, 2026 10:37
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

Fix all issues with AI Agents 🤖
In @cortex/cli.py:
- Around line 972-973: Remove the redundant local "import sys" statement in
cortex/cli.py (the duplicate import of the sys module found inside the
function/block near the CLI logic); sys is already imported at module top, so
delete that local import to avoid duplication and rely on the module-level sys
import instead.

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)

1-1: Fix Black formatting to pass CI.

The pipeline indicates Black formatting check failed. Run black . to reformat the code before merging.

♻️ Duplicate comments (1)
cortex/cli.py (1)

63-78: Provider/key mismatch remains a concern for ask() command.

As noted in a past review, when CORTEX_PROVIDER=openai is set but OPENAI_API_KEY is missing, this returns "ollama-local" while _get_provider() still returns "openai". This causes a mismatch in the ask() command flow (lines 532-561) where both values are passed to AskHandler.

The install() method now uses _get_api_key_for_provider() directly, mitigating the issue there, but ask() still relies on this potentially mismatched pair.

Consider either:

  1. Making _get_api_key() return None for non-ollama providers without valid keys (letting the error path handle it), or
  2. Updating ask() to use _get_api_key_for_provider() directly like install() does.
🧹 Nitpick comments (2)
cortex/cli.py (2)

598-609: Unused provider variable and questionable "dummy-key" fallback.

  1. Line 599: The provider variable is assigned but never used after the loop. The # noqa: F841 suggests awareness, but consider removing the assignment if truly unused.

  2. Line 605: Using "dummy-key" when _get_api_key_for_provider returns None will cause authentication failures with cloud providers. While the exception handler catches this and moves to the next provider, it's inefficient and produces unnecessary API errors.

Consider skipping providers with no valid key:

🔎 Proposed fix
         for try_provider in providers_to_try:
             try:
-                try_api_key = self._get_api_key_for_provider(try_provider) or "dummy-key"
+                try_api_key = self._get_api_key_for_provider(try_provider)
+                if not try_api_key:
+                    self._debug(f"No API key for {try_provider}, skipping")
+                    continue
                 self._debug(f"Trying provider: {try_provider}")

627-629: Exception handling is functional but could be more specific.

Catching (RuntimeError, Exception) is redundant since Exception includes RuntimeError. The broad catch allows fallback on any error but may hide unexpected issues. Consider explicitly listing expected exceptions:

except (ValueError, RuntimeError, OSError) as e:

This is a minor style suggestion; the current implementation works correctly.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a8b0b5 and 5c2b554.

📒 Files selected for processing (3)
  • cortex/cli.py
  • tests/test_cli.py
  • tests/test_cli_extended.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_cli.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 (1)
  • test_get_api_key_not_found (40-48)
cortex/cli.py (2)
  • _get_api_key (63-78)
  • install (563-826)
cortex/cli.py (3)
cortex/first_run_wizard.py (2)
  • FirstRunWizard (268-875)
  • run (389-599)
cortex/llm/interpreter.py (2)
  • CommandInterpreter (18-388)
  • parse (320-378)
cortex/branding.py (1)
  • cx_print (49-69)
🪛 GitHub Actions: CI
cortex/cli.py

[error] 1-1: Black formatting check failed: 1 file would be reformatted. Run 'black .' to format.

⏰ 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: Agent
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
🔇 Additional comments (8)
tests/test_cli_extended.py (4)

43-46: LGTM! Simplified test aligns with provider-first resolution.

The test now correctly uses CORTEX_PROVIDER=ollama to bypass API key detection, which matches the updated _get_api_key logic that returns "ollama-local" when provider is explicitly set to ollama. The removal of Path.home patching and user input simulation is appropriate since the explicit provider takes precedence.


95-102: LGTM! Test reflects new fallback behavior.

The updated test correctly expects install() to succeed (return 0) when CommandInterpreter returns valid commands, even without an explicit API key. This aligns with the new multi-provider probing logic that falls back to ollama-local when no cloud provider keys are available.


104-144: LGTM! Environment-driven test setup is consistent.

The addition of @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-key"}, clear=True) to these tests properly aligns with the environment-driven API key resolution. This approach is more realistic than mocking internal methods and matches the production behavior.


144-144: Good addition of parse assertion.

Adding the assert_called_once_with("install docker") assertion verifies that the interpreter receives the expected input, improving test coverage and consistency with test_install_dry_run.

cortex/cli.py (4)

21-21: LGTM!

The FirstRunWizard import is correctly placed with other cortex module imports and is necessary for the updated wizard functionality.


47-61: LGTM with minor observation on key validation.

The provider-specific key retrieval logic is well-structured. The prefix validation (sk-ant- for Anthropic, sk- for OpenAI) provides basic key format checking.

Note: OpenAI keys may use various prefixes (e.g., sk-proj-), but the current sk- check covers these cases adequately.


961-968: LGTM! Clean wizard delegation.

The wizard method now properly delegates to FirstRunWizard with interactive=True, returning appropriate exit codes. This aligns with the PR objective to improve the first-time user experience.


1043-1043: LGTM!

The simplified install instruction for cryptography is cleaner and more direct.

Comment on lines +972 to +973
import sys

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

Remove redundant import sys.

sys is already imported at the module level (line 4). This local import is unnecessary.

🔎 Proposed fix
     def env(self, args: argparse.Namespace) -> int:
         """Handle environment variable management commands."""
-        import sys
-
         env_mgr = get_env_manager()
🤖 Prompt for AI Agents
In @cortex/cli.py around lines 972-973, Remove the redundant local "import sys"
statement in cortex/cli.py (the duplicate import of the sys module found inside
the function/block near the CLI logic); sys is already imported at module top,
so delete that local import to avoid duplication and rely on the module-level
sys import instead.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses issue #355 by fixing the wizard logic to detect and honor API keys already present in environment variables or .env files. The wizard now allows setup to continue instead of exiting prematurely when a valid key is detected, improving the first-time user experience.

Key Changes

  • Enhanced API key detection logic that checks both .env files and shell-exported environment variables
  • Refactored wizard flow to offer provider selection with indicators for detected keys
  • Added comprehensive test coverage for API key validation and environment variable priority handling

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 19 comments.

Show a summary per file
File Description
cortex/first_run_wizard.py Major refactoring of wizard flow with new API key detection, validation, and environment file handling functions
cortex/cli.py Updated provider fallback logic and wizard integration; modified env import return behavior
cortex/utils/api_key_validator.py New file with API validation utilities for Anthropic and OpenAI keys
cortex/env_loader.py Extended .env file search locations to include parent directory and package directory
tests/test_first_run_wizard.py Added comprehensive tests for API key validation, environment variable priority, and wizard flow
tests/test_cli.py Modified test to use updated API key detection approach
tests/test_cli_extended.py Simplified tests by removing redundant mocks and using environment variables directly
tests/installer/test_parallel_install.py Updated Python command from 'python' to 'python3' for better compatibility
test_parallel_llm.py New test file for parallel LLM functionality verification
examples/parallel_llm_demo.py Contains duplicate line that needs to be removed
examples/env_demo.py Moved future import to top of file per PEP 8
.env.example Contains unresolved merge conflict markers
cortex/llm/interpreter.py Minor formatting improvement (added line break for readability)
cortex/dependency_check.py Minor formatting improvement (standardized string joining)
tests/test_env_manager.py Updated URL validation test (sftp→ftp)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

self.config["api_provider"] = "anthropic"
self.config["api_key_configured"] = True

random_example = random.choice(DRY_RUN_EXAMPLES) # noqa: S311
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

The noqa comment "S311" is used to suppress the security warning about using random.choice for cryptographic purposes. However, this is not a security-sensitive context (just selecting a random example for display), so the comment is appropriate. For consistency with Python security best practices, consider using secrets.choice() instead if this code might be used in any security-sensitive context in the future, or document why random.choice is sufficient here.

Copilot uses AI. Check for mistakes.
providers_to_try.append(other_provider)

commands = None
provider = None # noqa: F841 - assigned in loop
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

The noqa comment states "assigned in loop", but the variable 'provider' is initialized to None and assigned in the loop at line 622. However, if the loop completes without finding any valid commands, 'provider' remains None and is never used afterward. The noqa comment appears to be suppressing a false positive from the linter. Consider removing this variable initialization entirely since it's not used outside the loop - only try_provider is needed.

Copilot uses AI. Check for mistakes.
Comment on lines 167 to 169
key_from_env = os.environ.get(env_var)
if key_from_env is not None and len(key_from_env.strip()) > 0:
key_from_env = key_from_env.strip()
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

Redundant condition. Line 168 checks 'key_from_env is not None and len(key_from_env.strip()) > 0', but this could be simplified. Since you're immediately stripping the value on line 169, you could combine these operations: 'key_from_env = os.environ.get(env_var, "").strip()' and then just check 'if key_from_env:'.

Suggested change
key_from_env = os.environ.get(env_var)
if key_from_env is not None and len(key_from_env.strip()) > 0:
key_from_env = key_from_env.strip()
key_from_env = os.environ.get(env_var, "").strip()
if key_from_env:

Copilot uses AI. Check for mistakes.
from typing import Any

# Import API key validation utilities
from cortex.utils.api_key_validator import validate_anthropic_api_key, validate_openai_api_key
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

The API key validation utilities validate_anthropic_api_key and validate_openai_api_key are imported but never used in this file. The code uses a simpler format-based validation (checking prefixes like 'sk-ant-' and 'sk-') instead of making actual API calls to validate the keys. Either remove these unused imports or update the validation logic to use these functions for more robust validation.

Copilot uses AI. Check for mistakes.
self._print_error(str(e))
if "cryptography" in str(e).lower():
cx_print("Install with: pip install cryptography", "info")
cx_print("Install with: pip install cryptography", "info")
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

The removed conditional check 'if "cryptography" in str(e).lower()' was previously used to provide helpful context only when the error was specifically about the cryptography library. Now the "Install with: pip install cryptography" message is shown for ALL ImportError exceptions, even if they're unrelated to cryptography. This could mislead users. Consider restoring the conditional check to only show the installation hint when it's actually relevant.

Suggested change
cx_print("Install with: pip install cryptography", "info")
if "cryptography" in str(e).lower():
cx_print("Install with: pip install cryptography", "info")

Copilot uses AI. Check for mistakes.

print(f"Processing {len(requests)} requests with max_concurrent=2...")
start = time.time()
responses = await router.complete_batch(requests, max_concurrent=2)
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

Variable responses is not used.

Copilot uses AI. Check for mistakes.
start_time = time.time()

responses = await query_multiple_packages(router, packages, max_concurrent=5)
responses = await query_multiple_packages(router, packages, max_concurrent=5)
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

This assignment to 'responses' is unnecessary as it is redefined before this value is used.

Suggested change
responses = await query_multiple_packages(router, packages, max_concurrent=5)

Copilot uses AI. Check for mistakes.

cortex_dir = Path(cortex.__file__).parent / ".env"
locations.append(cortex_dir)
except ImportError:
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except ImportError:
except ImportError:
# The cortex package may not be installed; skip its optional .env location.

Copilot uses AI. Check for mistakes.
Comment on lines 103 to 104
except Exception:
pass
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except Exception:
pass
except Exception as e:
logger.warning(f"Error reading existing .env file at {env_path}: {e}")

Copilot uses AI. Check for mistakes.
# Load .env file but don't override existing shell-exported keys
# This preserves keys set via `export ANTHROPIC_API_KEY=xxx`
load_dotenv(dotenv_path=env_path, override=False)
except ImportError:
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except ImportError:
except ImportError:
# python-dotenv is optional; if it's not installed, we simply
# skip loading from .env and rely on existing environment vars.

Copilot uses AI. Check for mistakes.
- Apply black formatting to cli.py
- Fix test_no_key_found to also patch Path.cwd() to prevent finding local .env file
@Anshgrover23
Copy link
Collaborator

Anshgrover23 commented Jan 7, 2026

@Suyashd999 @jaysurse Not needed now, See for more info: #355 (comment)

@Suyashd999 Suyashd999 reopened this Jan 9, 2026
@Anshgrover23 Anshgrover23 marked this pull request as draft January 9, 2026 11:38
@mikejmorgan-ai mikejmorgan-ai self-assigned this Jan 10, 2026
Copy link
Collaborator

@Anshgrover23 Anshgrover23 left a comment

Choose a reason for hiding this comment

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

@jaysurse Kindly resolve conflicts.

Jay Surse and others added 3 commits January 10, 2026 21:34
- Add backward compatibility functions to api_key_detector
- Update wizard to allow reconfiguration after setup
- Fix wizard to continue flow when keys are found
- Update tests for proper mocking
@jaysurse jaysurse marked this pull request as ready for review January 10, 2026 17:14
Copy link
Collaborator

@Anshgrover23 Anshgrover23 left a comment

Choose a reason for hiding this comment

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

@jaysurse CI checks failing.Kindly complete your CLA verification, making it draft until then.

@Anshgrover23 Anshgrover23 marked this pull request as draft January 10, 2026 17:15
@jaysurse jaysurse marked this pull request as ready for review January 10, 2026 17:16
@jaysurse jaysurse marked this pull request as draft January 10, 2026 17:17
- Rename test_install_no_api_key to test_install_with_openai_key
- Fix duplicate function name causing CI lint failure
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: 5

Caution

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

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

156-168: Confirm the intended set of “valid URL” schemes (sftp vs ftps).

This test change tightens/changes accepted schemes; make sure EnvironmentValidator.validate(..., "url") intentionally allows ftps:// (and whether sftp:// should remain valid or be explicitly rejected/untested).

tests/test_cli.py (1)

83-120: Fix CI blocker: duplicate test name (Ruff F811) + invalid monkeypatch usage in unittest.TestCase.

Proposed fix
@@
     @patch.dict(os.environ, {}, clear=True)
     @patch("cortex.cli.CommandInterpreter")
     def test_install_no_api_key(self, mock_interpreter_class):
@@
         self.assertEqual(result, 0)
 
-    @patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-openai-key-123"}, clear=True)
-    @patch("cortex.cli.CommandInterpreter")
-    def test_install_no_api_key(self, mock_interpreter_class):
-        # Should work with Ollama (no API key needed)
-        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")
-        # Should succeed with Ollama as fallback provider
-        self.assertEqual(result, 0)
-
-    @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")
cortex/cli.py (1)

645-716: Don’t attempt API providers with placeholder keys; skip them when no real key exists.

try_api_key = ... or "dummy-key" means you’ll potentially call provider clients with invalid credentials and rely on exception control flow. It’s safer and faster to only attempt claude/openai when a real key exists (while still allowing ollama/fake).

Proposed fix
         for try_provider in providers_to_try:
             try:
-                try_api_key = self._get_api_key_for_provider(try_provider) or "dummy-key"
+                try_api_key = self._get_api_key_for_provider(try_provider)
+                if try_provider not in ["ollama", "fake"] and not try_api_key:
+                    self._debug(f"Skipping provider {try_provider}: no API key configured")
+                    continue
                 self._debug(f"Trying provider: {try_provider}")
                 interpreter = CommandInterpreter(api_key=try_api_key, provider=try_provider)
🤖 Fix all issues with AI agents
In @cortex/first_run_wizard.py:
- Around line 223-260: The detect_available_providers function currently returns
early after get_detected_provider(), which can miss other valid providers;
remove the early return and instead always aggregate providers by checking
detect_api_key(provider) (for each SUPPORTED_PROVIDERS) and/or
get_valid_api_key("ANTHROPIC_API_KEY","anthropic") and
get_valid_api_key("OPENAI_API_KEY","openai") so all valid providers are
collected, and still include shutil.which("ollama") if present; keep the
try/except around get_detected_provider() but merge its result into the
providers list without exiting early.
- Around line 105-153: save_key_to_env_file currently writes plaintext keys
directly and can produce partial/corrupted files and loose permissions; fix it
by performing an atomic write to a temp file in the same directory and then
atomically replacing the target with os.replace (or similar) after fsyncing the
temp file, and ensure the final .env file has strict permissions (chmod 0o600)
and the ~/.cortex directory is created with restrictive perms (0o700). Implement
this in save_key_to_env_file: create parent dir with mode 0o700, write new_lines
to a temporary file in that directory (fsync and close), os.replace temp →
env_path, then os.chmod(env_path, 0o600); catch and log OSErrors as before and
return False on failure.

In @tests/test_first_run_wizard.py:
- Around line 204-215: The test test_get_valid_api_key_no_key_anywhere currently
patches detect_api_key via "cortex.first_run_wizard.detect_api_key", which will
fail if the api_key_detector module isn't present; instead modify the test to
avoid relying on that import by using monkeypatch to set detect_api_key on the
first_run_wizard module with raising=False (e.g., import cortex.first_run_wizard
as frw and call monkeypatch.setattr(frw, "detect_api_key", lambda *a, **k: None,
raising=False)); keep mocking read_key_from_env_file as before and call
get_valid_api_key unchanged.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c09517 and c5ce259.

📒 Files selected for processing (7)
  • cortex/api_key_detector.py
  • cortex/cli.py
  • cortex/first_run_wizard.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 (1)
  • 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_env_manager.py
  • cortex/api_key_detector.py
  • tests/test_first_run_wizard.py
  • cortex/cli.py
  • cortex/first_run_wizard.py
  • tests/test_cli.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_env_manager.py
  • tests/test_first_run_wizard.py
  • tests/test_cli.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 **/*.py : Docstrings required for all public APIs

Applied to files:

  • cortex/first_run_wizard.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 **/*install*.py : Dry-run by default for all installations in command execution

Applied to files:

  • tests/test_cli.py
🧬 Code graph analysis (3)
cortex/cli.py (3)
cortex/first_run_wizard.py (2)
  • FirstRunWizard (340-978)
  • run (670-764)
cortex/installation_history.py (1)
  • InstallationHistory (74-632)
cortex/llm/interpreter.py (2)
  • CommandInterpreter (20-373)
  • parse (305-363)
cortex/first_run_wizard.py (2)
cortex/api_key_detector.py (2)
  • detect_api_key (698-711)
  • get_detected_provider (714-722)
cortex/env_manager.py (1)
  • get_env_manager (1108-1110)
tests/test_cli.py (1)
tests/test_cli_extended.py (2)
  • test_install_no_api_key (96-102)
  • test_install_dry_run (110-125)
🪛 GitHub Actions: CI
tests/test_cli.py

[error] 97-97: F811 Redefinition of unused test_install_no_api_key from line 85: test_install_no_api_key redefined here

🪛 GitHub Check: lint
tests/test_cli.py

[failure] 97-97: Ruff (F811)
tests/test_cli.py:97:9: F811 Redefinition of unused test_install_no_api_key from line 85: test_install_no_api_key redefined here

🪛 GitHub Check: Lint
tests/test_cli.py

[failure] 97-97: Ruff (F811)
tests/test_cli.py:97:9: F811 Redefinition of unused test_install_no_api_key from line 85: test_install_no_api_key redefined here

⏰ 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)
cortex/first_run_wizard.py (2)

564-591: Dry-run verification flow is a good UX improvement.


592-630: Keeping existing env-exported keys and continuing the flow matches the PR objective.

tests/test_first_run_wizard.py (1)

13-24: Key-resolution test coverage is solid and matches the new priority rules.

Also applies to: 134-216

cortex/cli.py (1)

1041-1048: Wizard delegation is clean and keeps CLI flow simple.

Comment on lines +697 to +725
# Convenience functions for backward compatibility
def detect_api_key(provider: str) -> str | None:
"""
Detect API key for a specific provider.
Args:
provider: The provider name ('anthropic', 'openai')
Returns:
The API key or None if not found
"""
found, key, detected_provider, source = auto_detect_api_key()
if found and detected_provider == provider:
return key
return None


def get_detected_provider() -> str | None:
"""
Get the detected provider name.
Returns:
The provider name or None if not detected
"""
found, key, provider, source = auto_detect_api_key()
return provider if found else None


SUPPORTED_PROVIDERS = ["anthropic", "openai", "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 | 🟠 Major

detect_api_key() is not truly provider-scoped (and docstring disagrees with SUPPORTED_PROVIDERS).

Right now detect_api_key(provider) returns a key only if the globally-detected provider matches provider, which can hide other available keys (e.g., both keys present but only Anthropic is “detected”). Also the docstring doesn’t mention "ollama" even though it’s now “supported”.

Proposed direction (keep backward-compat but make semantics predictable)
 def detect_api_key(provider: str) -> str | None:
@@
-    found, key, detected_provider, source = auto_detect_api_key()
-    if found and detected_provider == provider:
-        return key
-    return None
+    # Provider-scoped check: look at the specific env var first, then fall back to full detection.
+    provider = provider.lower().strip()
+    if provider == "anthropic":
+        key = os.environ.get("ANTHROPIC_API_KEY")
+        return key if key else None
+    if provider == "openai":
+        key = os.environ.get("OPENAI_API_KEY")
+        return key if key else None
+    if provider == "ollama":
+        # Presence-based; callers can treat "ollama-local" as configured.
+        return "ollama-local" if os.environ.get("CORTEX_PROVIDER", "").lower() == "ollama" else None
+
+    found, key, detected_provider, _source = auto_detect_api_key()
+    return key if found and detected_provider == provider else None

(If you want this to search non-env locations per provider too, the clean solution is to add a real provider-scoped search inside APIKeyDetector rather than reusing the global detect() path.)

Comment on lines +129 to 160
def _get_api_key_for_provider(self, provider: str) -> str | None:
"""Get API key for a specific provider."""
if provider == "ollama":
return "ollama-local"
if provider == "fake":
return "fake-key"
if provider == "claude":
key = os.environ.get("ANTHROPIC_API_KEY")
if key and key.strip().startswith("sk-ant-"):
return key.strip()
elif provider == "openai":
key = os.environ.get("OPENAI_API_KEY")
if key and key.strip().startswith("sk-"):
return key.strip()
return None

# 2. Try auto-detection + prompt to save (setup_api_key handles both)
success, key, detected_provider = setup_api_key()
if success:
self._debug(f"Using {detected_provider} API key")
# Store detected provider so _get_provider can use it
self._detected_provider = detected_provider
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

# Still no key
self._print_error("No API key found or provided")
# 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")
):
return "ollama-local"
# Otherwise, prompt user for setup
self._print_error("No valid API key found.")
cx_print("Run [bold]cortex wizard[/bold] to configure your API key.", "info")
cx_print("Or use [bold]CORTEX_PROVIDER=ollama[/bold] for offline mode.", "info")
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 | 🟠 Major

Align key validation with wizard rules and avoid misclassifying Anthropic keys as OpenAI.

Proposed fix
     def _get_api_key_for_provider(self, provider: str) -> str | None:
         """Get API key for a specific provider."""
@@
         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()
         return None

Comment on lines +105 to +153
def save_key_to_env_file(key_name: str, key_value: str) -> bool:
"""Save an API key to the .env file.
Saves to ~/.cortex/.env which is checked by api_key_detector (priority #2).
Args:
key_name: The environment variable name.
key_value: The value to save.
Returns:
True if saved successfully, False otherwise.
"""
env_path = get_env_file_path()
env_path.parent.mkdir(parents=True, exist_ok=True)

lines: list[str] = []
key_found = False

if env_path.exists():
try:
with open(env_path) as f:
lines = f.readlines()
except OSError:
pass

new_lines: list[str] = []
for line in lines:
stripped = line.strip()
if stripped and not stripped.startswith("#") and "=" in stripped:
existing_key = stripped.split("=")[0].strip()
if existing_key == key_name:
new_lines.append(f'{key_name}="{key_value}"\n')
key_found = True
continue
new_lines.append(line)

if not key_found:
if new_lines and not new_lines[-1].endswith("\n"):
new_lines.append("\n")
new_lines.append(f'{key_name}="{key_value}"\n')

try:
with open(env_path, "w") as f:
f.writelines(new_lines)
return True
except OSError as e:
logger.warning("Error saving to .env file: %s", e)
return False

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

Secure the ~/.cortex/.env write path (permissions + atomic write).

save_key_to_env_file() writes plaintext API keys but doesn’t enforce 0600 or atomic replace, so keys can be overexposed or the file can be corrupted on partial writes.

Proposed fix
 def save_key_to_env_file(key_name: str, key_value: str) -> bool:
@@
-    try:
-        with open(env_path, "w") as f:
-            f.writelines(new_lines)
-        return True
+    try:
+        tmp_path = env_path.with_suffix(env_path.suffix + ".tmp")
+        with open(tmp_path, "w", encoding="utf-8") as f:
+            f.writelines(new_lines)
+        os.chmod(tmp_path, 0o600)
+        os.replace(tmp_path, env_path)
+        os.chmod(env_path, 0o600)
+        return True
     except OSError as e:
         logger.warning("Error saving to .env file: %s", e)
         return False
🤖 Prompt for AI Agents
In @cortex/first_run_wizard.py around lines 105 - 153, save_key_to_env_file
currently writes plaintext keys directly and can produce partial/corrupted files
and loose permissions; fix it by performing an atomic write to a temp file in
the same directory and then atomically replacing the target with os.replace (or
similar) after fsyncing the temp file, and ensure the final .env file has strict
permissions (chmod 0o600) and the ~/.cortex directory is created with
restrictive perms (0o700). Implement this in save_key_to_env_file: create parent
dir with mode 0o700, write new_lines to a temporary file in that directory
(fsync and close), os.replace temp → env_path, then os.chmod(env_path, 0o600);
catch and log OSErrors as before and return False on failure.

Comment on lines +223 to +260
def detect_available_providers() -> list[str]:
"""Detect available providers based on valid API keys.
Uses api_key_detector if available to check all supported locations.
Returns:
List of available provider names ('anthropic', 'openai', 'ollama').
"""
providers = []

# Use api_key_detector if available
if HAS_API_KEY_DETECTOR:
try:
detected = get_detected_provider()
if detected:
providers.append(detected)
# Check for additional providers
for provider in SUPPORTED_PROVIDERS:
if provider not in providers:
key = detect_api_key(provider)
if key:
providers.append(provider)
if shutil.which("ollama") and "ollama" not in providers:
providers.append("ollama")
return providers
except Exception as e:
logger.debug("api_key_detector detection failed: %s", e)

# Fallback detection
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

Provider availability detection can miss valid providers when api_key_detector is present.

detect_available_providers() returns early after get_detected_provider(), but auto_detect_api_key() is priority-based and may only ever “detect” one provider even if multiple keys exist. This can hide OpenAI availability when Anthropic is also present (and vice versa).

Minimal fix: don’t early-return; always aggregate via get_valid_api_key()
 def detect_available_providers() -> list[str]:
@@
-    # Use api_key_detector if available
-    if HAS_API_KEY_DETECTOR:
-        try:
-            detected = get_detected_provider()
-            if detected:
-                providers.append(detected)
-                # Check for additional providers
-                for provider in SUPPORTED_PROVIDERS:
-                    if provider not in providers:
-                        key = detect_api_key(provider)
-                        if key:
-                            providers.append(provider)
-                if shutil.which("ollama") and "ollama" not in providers:
-                    providers.append("ollama")
-                return providers
-        except Exception as e:
-            logger.debug("api_key_detector detection failed: %s", e)
+    # Optionally consult api_key_detector for telemetry, but don't let it short-circuit
+    # provider aggregation (it's "best match" detection, not "all providers").
+    if HAS_API_KEY_DETECTOR:
+        try:
+            detected = get_detected_provider()
+            if detected and detected not in providers:
+                providers.append(detected)
+        except Exception as e:
+            logger.debug("api_key_detector detection failed: %s", e)
 
     # Fallback detection
     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
🤖 Prompt for AI Agents
In @cortex/first_run_wizard.py around lines 223 - 260, The
detect_available_providers function currently returns early after
get_detected_provider(), which can miss other valid providers; remove the early
return and instead always aggregate providers by checking
detect_api_key(provider) (for each SUPPORTED_PROVIDERS) and/or
get_valid_api_key("ANTHROPIC_API_KEY","anthropic") and
get_valid_api_key("OPENAI_API_KEY","openai") so all valid providers are
collected, and still include shutil.which("ollama") if present; keep the
try/except around get_detected_provider() but merge its result into the
providers list without exiting early.

Copy link
Collaborator

@Anshgrover23 Anshgrover23 left a comment

Choose a reason for hiding this comment

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

@jaysurse Pull latest changes and updated your branch.
Kindly address all comments.

@sonarqubecloud
Copy link

@Anshgrover23 Anshgrover23 marked this pull request as ready for review January 16, 2026 13:09
Copy link
Collaborator

@Anshgrover23 Anshgrover23 left a comment

Choose a reason for hiding this comment

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

@jaysurse jay@cortex.local CLA is missing.

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.

[FEATURE] Add API key reconfiguration Cortex Wizard

5 participants