Skip to content

Conversation

@murataslan1
Copy link
Contributor

@murataslan1 murataslan1 commented Jan 3, 2026

  • MVP Implementation
  • Install/Remove/List
  • Storage Analysis (cortex pkg storage)
  • Permissions Check (cortex pkg permissions)
  • Docstrings (>80%)
  • CLI Help

Closes #450.

Uploading Screen Recording 2026-01-08 at 14.31.53.mov…

AI Usage

This PR uses AI-generated code for:

  • Input validation patterns (regex)
  • Refactoring duplication in package_manager.py
  • Unit test generation (tests/test_package_manager.py)
  • Documentation generation (docs/modules/README_PACKAGE_MANAGER.md)

CLA

I have read and signed the CLA.

Summary by CodeRabbit

  • New Features
    • Added package management commands: install, remove, list, storage, and permissions
    • Supports Snap and Flatpak with automatic backend detection
    • Prompts user to choose when both backends are available
    • Dry-run support to preview operations
    • Accepts an alternative command alias ("apps") for package operations

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

Copilot AI review requested due to automatic review settings January 3, 2026 09:18
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 3, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Added a new pkg CLI command and a UnifiedPackageManager class that detect Snap/Flatpak backends and perform install, remove, list, storage, and permissions actions, with backend selection, dry-run support, and subprocess-based command execution and error handling.

Changes

Cohort / File(s) Summary
CLI Integration
cortex/cli.py
Added pkg(self, args: argparse.Namespace) -> int, integrated a pkg subparser (subcommands: install, remove, list, storage, permissions) with flags like --dry-run, and dispatches pkg (also aliased as apps) to the new handler.
Package Manager
cortex/package_manager.py
New UnifiedPackageManager class: detects presence of snap and flatpak, exposes install, remove, list_packages, storage_analysis, check_permissions, includes _choose_backend, backend-specific command builders (_get_install_cmd, _get_remove_cmd), and _run_cmd which supports dry-run and subprocess execution with error handling.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as CortexCLI
    participant PM as UnifiedPackageManager
    participant Sys as System

    User->>CLI: pkg install <package> [--dry-run]
    CLI->>PM: install(package, dry_run)
    PM->>PM: check_backends()

    alt both backends available
        PM->>User: prompt backend choice
        User-->>PM: choose backend
    else one backend available
        PM-->>PM: select available backend
    else no backends
        PM-->>CLI: warn no backends
    end

    PM->>PM: _get_install_cmd(backend, package)

    alt dry-run
        PM-->>CLI: print dry-run info
    else execute
        PM->>Sys: run install command (subprocess)
        Sys-->>PM: success / error
    end

    PM-->>CLI: return exit code
    CLI-->>User: display status
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰
Hop, little package, through snap and flat,
I sniff the backends, pick this or that.
Dry-run I whisper, or run with a cheer,
Install, list, remove — the choice now is clear.
A rabbit's small dance for packages near.

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is missing required template sections including Related Issue details, Summary, and AI Disclosure information. Add the missing required sections: provide a clear Summary of changes, explicitly declare AI tool usage with the checkbox and description, and ensure the Related Issue section is properly formatted with issue number context.
Linked Issues check ❓ Inconclusive The PR implements most core requirements: install/remove, list packages, storage analysis, and permissions check. However, it only partially addresses comparing options and showing true package sources. Verify that the implementation can show true package sources and compare installation options across deb/snap/flatpak formats as specified in issue #450.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: implementing a unified Snap/Flatpak package manager with bounty reference #450.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the unified package manager CLI command and backend logic; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 93.33% which is sufficient. The required threshold is 80.00%.

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

@github-actions
Copy link

github-actions bot commented Jan 3, 2026

CLA Verification Passed

All contributors have signed the CLA.

Contributor Signed As
@murataslan1 @murataslan1
@Anshgrover23 @Anshgrover23

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

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

8-11: Consider expanding the docstring.

The class docstring could benefit from examples or a brief description of key methods. This would improve developer ergonomics, especially for a new feature.

Example enhanced docstring
 class UnifiedPackageManager:
     """
     Unified manager for Snap and Flatpak packages.
+    
+    Provides a consistent interface to install, remove, and list packages
+    from both Snap and Flatpak backends. Automatically detects available
+    backends and prompts user to choose when both are present.
+    
+    Example:
+        mgr = UnifiedPackageManager()
+        mgr.install("firefox", dry_run=True)
+        mgr.list_packages()
     """

63-76: Verify the fallback behavior when no backends are available.

The method returns "snap" (line 76) when neither backend is available. While the comment mentions this is for "testing/dry-run", this could be confusing in practice:

  • If a user runs cortex pkg install firefox without either backend, they'll get prompted to choose, then see "Executable not found: sudo" (from line 103)
  • The warning from check_backends() appears earlier, but the flow continues as if snap is available

Consider returning None here to make the "no backend available" case more explicit, or at minimum, add a clear warning message.

Alternative approach
     def _choose_backend(self, action: str) -> Optional[str]:
         if self.snap_avail and self.flatpak_avail:
             return Prompt.ask(
                 f"Choose backend to {action}", 
                 choices=["snap", "flatpak"], 
                 default="snap"
             )
         elif self.snap_avail:
             return "snap"
         elif self.flatpak_avail:
             return "flatpak"
         else:
-            # Fallback for testing/dry-run if forced, or just default to snap for print
-            return "snap" 
+            cx_print("No package backend available. Cannot proceed.", "error")
+            return None

This makes the error handling more explicit and prevents attempting operations that will fail.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0a256b and 67b2498.

📒 Files selected for processing (2)
  • cortex/cli.py
  • cortex/package_manager.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/package_manager.py
🧬 Code graph analysis (2)
cortex/cli.py (3)
cortex/package_manager.py (4)
  • UnifiedPackageManager (8-103)
  • install (21-29)
  • remove (31-39)
  • list_packages (41-61)
cortex/sandbox/docker_sandbox.py (1)
  • install (419-481)
cortex/first_run_wizard.py (1)
  • _print_error (746-748)
cortex/package_manager.py (1)
cortex/branding.py (2)
  • cx_print (49-69)
  • cx_header (82-88)
⏰ 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). (1)
  • GitHub Check: Agent
🔇 Additional comments (3)
cortex/cli.py (3)

1578-1578: LGTM!

The help table entry is clear and consistent with other commands.


1883-1902: LGTM!

The parser configuration correctly defines the pkg subcommand structure with appropriate arguments and follows the established pattern in the codebase.


1949-1950: LGTM!

The dispatch logic correctly routes both the primary command and its alias to the pkg handler.

Comment on lines 1524 to 1545
def pkg(self, args: argparse.Namespace) -> int:
"""Handle package manager commands."""
try:
from cortex.package_manager import UnifiedPackageManager

mgr = UnifiedPackageManager()
action = getattr(args, "pkg_action", None)

if action == "install":
mgr.install(args.package, dry_run=args.dry_run)
elif action == "remove":
mgr.remove(args.package, dry_run=args.dry_run)
elif action == "list":
mgr.list_packages()
else:
self._print_error("Unknown package manager action")
return 1
return 0
except Exception as e:
self._print_error(f"Package Manager failed: {e}")
return 1

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

Handle missing pkg_action gracefully.

If pkg_action is None (e.g., when user runs cortex pkg without a subcommand), the current code will reach the else block and print "Unknown package manager action", which is misleading. Consider adding an explicit check at the start similar to the notify command pattern (lines 110-112).

🔎 Suggested fix
     def pkg(self, args: argparse.Namespace) -> int:
         """Handle package manager commands."""
         try:
             from cortex.package_manager import UnifiedPackageManager
 
             mgr = UnifiedPackageManager()
             action = getattr(args, "pkg_action", None)
+            
+            if not action:
+                self._print_error("Please specify a subcommand (install/remove/list)")
+                return 1
 
             if action == "install":
                 mgr.install(args.package, dry_run=args.dry_run)
             elif action == "remove":
                 mgr.remove(args.package, dry_run=args.dry_run)
             elif action == "list":
                 mgr.list_packages()
             else:
                 self._print_error("Unknown package manager action")
                 return 1
             return 0
🤖 Prompt for AI Agents
In cortex/cli.py around lines 1524 to 1545, the pkg command does not explicitly
handle a missing pkg_action and falls through to a misleading "Unknown package
manager action" message; add an immediate check after obtaining action (action =
getattr(args, "pkg_action", None)) that if action is None prints a clear error
(e.g., "No package manager action specified" or shows the pkg usage) and returns
1, before the install/remove/list branches so the no-subcommand case is handled
cleanly.

Comment on lines 12 to 14
def __init__(self):
self.snap_avail = shutil.which("snap") is not None
self.flatpak_avail = shutil.which("flatpak") is not None
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

Add type hints and docstring per coding guidelines.

The __init__ method is missing both type hints and a docstring, which are required by the project's coding guidelines for Python code.

🔎 Proposed enhancement
-    def __init__(self):
+    def __init__(self) -> None:
+        """Initialize the package manager and detect available backends."""
         self.snap_avail = shutil.which("snap") is not None
         self.flatpak_avail = shutil.which("flatpak") is not None

As per coding guidelines: "Type hints required in Python code" and "Docstrings required for all public APIs"

📝 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
def __init__(self):
self.snap_avail = shutil.which("snap") is not None
self.flatpak_avail = shutil.which("flatpak") is not None
def __init__(self) -> None:
"""Initialize the package manager and detect available backends."""
self.snap_avail = shutil.which("snap") is not None
self.flatpak_avail = shutil.which("flatpak") is not None
🤖 Prompt for AI Agents
In cortex/package_manager.py around lines 12 to 14, the __init__ method lacks
type hints and a docstring; add explicit type annotations for the method
signature (def __init__(self) -> None) and annotate the instance attributes
(self.snap_avail: bool and self.flatpak_avail: bool) and include a short
docstring describing what the initializer does and what the attributes represent
(e.g., checks availability of snap and flatpak). Ensure annotations use built-in
types and the docstring follows the project's docstring style.

Comment on lines 16 to 19
def check_backends(self):
if not self.snap_avail and not self.flatpak_avail:
cx_print("Warning: Neither 'snap' nor 'flatpak' found on this system.", "warning")
cx_print("Commands will run in DRY-RUN mode or fail.", "info")
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

Add type hints and docstring per coding guidelines.

This public method is missing both type hints and a docstring, which are required by the project's coding guidelines.

🔎 Proposed enhancement
-    def check_backends(self):
+    def check_backends(self) -> None:
+        """Check if snap or flatpak backends are available and warn if neither is found."""
         if not self.snap_avail and not self.flatpak_avail:
             cx_print("Warning: Neither 'snap' nor 'flatpak' found on this system.", "warning")
             cx_print("Commands will run in DRY-RUN mode or fail.", "info")

As per coding guidelines.

🤖 Prompt for AI Agents
In cortex/package_manager.py around lines 16–19, the public method
check_backends is missing type hints and a docstring; update the signature to
include a return type (-> None) and, if applicable, an explicit self type (e.g.,
self) and add a concise triple-quoted docstring that states what the method
checks (presence of snap and flatpak), its side effects (calls cx_print with
warning/info), and any important behavior (e.g., when it triggers DRY-RUN mode
or failure); ensure the docstring follows project style and update imports only
if you add explicit typing requiring them.

Comment on lines 21 to 29
def install(self, package: str, dry_run: bool = False):
self.check_backends()

backend = self._choose_backend("install")
if not backend:
return

cmd = self._get_install_cmd(backend, package)
self._run_cmd(cmd, dry_run)
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

Add type hints and docstring per coding guidelines.

This public method is missing both type hints and a docstring, which are required by the project's coding guidelines.

🔎 Proposed enhancement
-    def install(self, package: str, dry_run: bool = False):
+    def install(self, package: str, dry_run: bool = False) -> None:
+        """
+        Install a package using Snap or Flatpak.
+        
+        Args:
+            package: Name of the package to install
+            dry_run: If True, only show what would be executed without running it
+        """
         self.check_backends()
         
         backend = self._choose_backend("install")
         if not backend:
             return
 
         cmd = self._get_install_cmd(backend, package)
         self._run_cmd(cmd, dry_run)

As per coding guidelines.

📝 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
def install(self, package: str, dry_run: bool = False):
self.check_backends()
backend = self._choose_backend("install")
if not backend:
return
cmd = self._get_install_cmd(backend, package)
self._run_cmd(cmd, dry_run)
def install(self, package: str, dry_run: bool = False) -> None:
"""
Install a package using Snap or Flatpak.
Args:
package: Name of the package to install
dry_run: If True, only show what would be executed without running it
"""
self.check_backends()
backend = self._choose_backend("install")
if not backend:
return
cmd = self._get_install_cmd(backend, package)
self._run_cmd(cmd, dry_run)
🤖 Prompt for AI Agents
In cortex/package_manager.py around lines 21 to 29, the public method install is
missing a docstring and a return type annotation; update the signature to
include an explicit return type (-> None) and add a concise docstring describing
what the method does, its parameters (package: str, dry_run: bool), the return
value (None), and any exceptions it may raise (e.g., if no backend found or
command fails); keep the docstring style consistent with the project's docstring
conventions (summary line, params, returns, raises).

Comment on lines 31 to 39
def remove(self, package: str, dry_run: bool = False):
self.check_backends()

backend = self._choose_backend("remove")
if not backend:
return

cmd = self._get_remove_cmd(backend, package)
self._run_cmd(cmd, dry_run)
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

Add type hints and docstring per coding guidelines.

This public method is missing both type hints and a docstring, which are required by the project's coding guidelines.

🔎 Proposed enhancement
-    def remove(self, package: str, dry_run: bool = False):
+    def remove(self, package: str, dry_run: bool = False) -> None:
+        """
+        Remove a package using Snap or Flatpak.
+        
+        Args:
+            package: Name of the package to remove
+            dry_run: If True, only show what would be executed without running it
+        """
         self.check_backends()
         
         backend = self._choose_backend("remove")
         if not backend:
             return
 
         cmd = self._get_remove_cmd(backend, package)
         self._run_cmd(cmd, dry_run)

As per coding guidelines.

📝 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
def remove(self, package: str, dry_run: bool = False):
self.check_backends()
backend = self._choose_backend("remove")
if not backend:
return
cmd = self._get_remove_cmd(backend, package)
self._run_cmd(cmd, dry_run)
def remove(self, package: str, dry_run: bool = False) -> None:
"""
Remove a package using Snap or Flatpak.
Args:
package: Name of the package to remove
dry_run: If True, only show what would be executed without running it
"""
self.check_backends()
backend = self._choose_backend("remove")
if not backend:
return
cmd = self._get_remove_cmd(backend, package)
self._run_cmd(cmd, dry_run)
🤖 Prompt for AI Agents
In cortex/package_manager.py around lines 31 to 39, the public method remove has
parameter type hints but is missing a return type annotation and a docstring;
add a return type of -> None to the signature and insert a concise triple-quoted
docstring immediately below the def describing what the method does, its
parameters (package: str, dry_run: bool), and that it returns None (and note any
exceptions it may raise if applicable), keeping wording brief and following the
project's docstring style.

Comment on lines 41 to 61
def list_packages(self):
cx_header("Installed Packages (Snap & Flatpak)")
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Package")
table.add_column("Backend")
table.add_column("Version", style="dim")

# Listings would require parsing output commands like `snap list` and `flatpak list`
# For MVP, we just show support status
if self.snap_avail:
table.add_row("Snap Support", "Detected", "Active")
else:
table.add_row("Snap Support", "Not Found", "-")

if self.flatpak_avail:
table.add_row("Flatpak Support", "Detected", "Active")
else:
table.add_row("Flatpak Support", "Not Found", "-")

console.print(table)
console.print("[dim](Full package listing implementation pending parsing logic)[/dim]")
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

Add type hints and docstring per coding guidelines.

This public method is missing both type hints and a docstring. Additionally, consider documenting in the docstring that this is currently a placeholder showing backend status rather than actual package listings.

🔎 Proposed enhancement
-    def list_packages(self):
+    def list_packages(self) -> None:
+        """
+        List installed packages from Snap and Flatpak.
+        
+        Note: Currently shows backend availability status only.
+        Full package listing requires parsing snap/flatpak command output.
+        """
         cx_header("Installed Packages (Snap & Flatpak)")
         table = Table(show_header=True, header_style="bold magenta")
         table.add_column("Package")
         table.add_column("Backend")
         table.add_column("Version", style="dim")
 
         # Listings would require parsing output commands like `snap list` and `flatpak list`
         # For MVP, we just show support status
         if self.snap_avail:
             table.add_row("Snap Support", "Detected", "Active")
         else:
             table.add_row("Snap Support", "Not Found", "-")
             
         if self.flatpak_avail:
             table.add_row("Flatpak Support", "Detected", "Active")
         else:
             table.add_row("Flatpak Support", "Not Found", "-")
             
         console.print(table)
         console.print("[dim](Full package listing implementation pending parsing logic)[/dim]")

As per coding guidelines.

🤖 Prompt for AI Agents
In cortex/package_manager.py around lines 41 to 61, the public method
list_packages is missing type hints and a docstring; update the signature to
include proper type hints (e.g., def list_packages(self) -> None) and add a
concise Google- or numpydoc-style docstring that states what the method does,
its parameters (self), return type (None), and explicitly documents that this is
a placeholder showing backend support status for Snap/Flatpak rather than a full
package listing until parsing logic is implemented; keep the docstring brief and
place it immediately under the def line.

Comment on lines 78 to 88
def _get_install_cmd(self, backend: str, package: str) -> List[str]:
if backend == "snap":
return ["sudo", "snap", "install", package]
else:
return ["flatpak", "install", "-y", package]

def _get_remove_cmd(self, backend: str, package: str) -> List[str]:
if backend == "snap":
return ["sudo", "snap", "remove", package]
else:
return ["flatpak", "uninstall", "-y", package]
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:

flatpak install command syntax remote package documentation

💡 Result:

Quick summary — syntax and common forms

  • Basic form: flatpak install [OPTION...] REMOTE NAME [BRANCH]. REMOTE is the configured remote name (or a file:// URI); NAME is the app/runtime ref (e.g. org.gnome.Gedit); BRANCH is optional when you need a non-default branch. [1][2]

  • Common options:

    • --user / --system / --installation=NAME — choose installation scope. [1][2]
    • --app or --runtime — restrict search to apps or runtimes. [2]
    • --bundle FILENAME or --from <.flatpakref|.flatpak> — install from a bundle or .flatpakref instead of a remote. [2]
    • -y/--assumeyes, --noninteractive — automation switches. [2]
  • Example:

    • flatpak install flathub org.gnome.Gedit
    • flatpak --user install test-repo org.gnome.GEdit
    • flatpak install --bundle /path/to/app.flatpak

Sources:
[1] Flatpak command reference / Using Flatpak (official docs).
[2] flatpak-install man page (flatpak install SYNOPSIS and OPTIONS).


Flatpak install command is missing the required remote source.

The flatpak install command at line 82 is incomplete. According to the official flatpak documentation, the syntax requires specifying a remote (e.g., "flathub") before the package name:

flatpak install [OPTIONS] REMOTE NAME

The current command ["flatpak", "install", "-y", package] omits the REMOTE argument, which will cause the command to fail.

Suggested fix
     def _get_install_cmd(self, backend: str, package: str) -> List[str]:
         if backend == "snap":
             return ["sudo", "snap", "install", package]
         else:
-            return ["flatpak", "install", "-y", package]
+            return ["flatpak", "install", "-y", "flathub", package]

Alternatively, consider making the remote configurable if different sources are needed.

🤖 Prompt for AI Agents
In cortex/package_manager.py around lines 78 to 88, the flatpak install command
is missing the required REMOTE argument so it will fail; update _get_install_cmd
to include a default remote (e.g., "flathub") before the package name
(["flatpak","install","-y","flathub", package]) or refactor to accept a
configurable remote (class attribute or additional parameter) and use that
value; leave _get_remove_cmd as-is (flatpak uninstall does not need a remote)
and ensure any callers are updated if you add a parameter.

Comment on lines 90 to 103
def _run_cmd(self, cmd: List[str], dry_run: bool):
cmd_str = " ".join(cmd)
if dry_run:
cx_print(f"[Dry Run] would execute: [bold]{cmd_str}[/bold]", "info")
return

cx_print(f"Running: {cmd_str}...", "info")
try:
subprocess.check_call(cmd)
cx_print("Command executed successfully.", "success")
except subprocess.CalledProcessError as e:
cx_print(f"Command failed: {e}", "error")
except FileNotFoundError:
cx_print(f"Executable not found: {cmd[0]}", "error")
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

Add type hints and docstring per coding guidelines.

This private method is missing a return type hint and a docstring. While docstrings for private methods are less critical, type hints are required by the coding guidelines.

🔎 Proposed enhancement
-    def _run_cmd(self, cmd: List[str], dry_run: bool):
+    def _run_cmd(self, cmd: List[str], dry_run: bool) -> None:
+        """
+        Execute or simulate execution of a command.
+        
+        Args:
+            cmd: Command and arguments as a list
+            dry_run: If True, only print the command without executing it
+        """
         cmd_str = " ".join(cmd)
         if dry_run:
             cx_print(f"[Dry Run] would execute: [bold]{cmd_str}[/bold]", "info")
             return
 
         cx_print(f"Running: {cmd_str}...", "info")
         try:
             subprocess.check_call(cmd)
             cx_print("Command executed successfully.", "success")
         except subprocess.CalledProcessError as e:
             cx_print(f"Command failed: {e}", "error")
         except FileNotFoundError:
              cx_print(f"Executable not found: {cmd[0]}", "error")

As per coding guidelines: "Type hints required in Python code"

🤖 Prompt for AI Agents
In cortex/package_manager.py around lines 90 to 103, the private method _run_cmd
has parameter type hints but is missing a return type hint and a docstring;
update the signature to include a return type (-> None) and add a concise
docstring describing the method's purpose, parameters (cmd, dry_run), behavior
(executes subprocess, prints dry-run message), and exceptions it may
raise/handle (subprocess.CalledProcessError, FileNotFoundError) so it adheres to
the coding guidelines.

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 adds a unified package manager interface for Snap and Flatpak package management through a new cortex pkg command. This addresses bounty #450 by providing a seamless way to install and remove applications using either Snap or Flatpak backends.

Key Changes:

  • New UnifiedPackageManager class that abstracts Snap/Flatpak operations
  • CLI integration with pkg command supporting install, remove, and list subcommands
  • Interactive backend selection when both Snap and Flatpak are available

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 18 comments.

File Description
cortex/package_manager.py Implements the core UnifiedPackageManager class with backend detection, command generation, and execution logic for Snap/Flatpak operations
cortex/cli.py Adds CLI handler for the pkg command, argument parsers for pkg subcommands (install/remove/list), and help table entry

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

Comment on lines 21 to 29
def install(self, package: str, dry_run: bool = False):
self.check_backends()

backend = self._choose_backend("install")
if not backend:
return

cmd = self._get_install_cmd(backend, package)
self._run_cmd(cmd, dry_run)
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

Missing input validation for the package parameter. The package name should be validated to prevent command injection attacks. For example, a package name like "; rm -rf /" could be dangerous when passed to subprocess. Consider adding validation to ensure the package name contains only allowed characters (alphanumeric, hyphens, underscores, etc.).

Copilot uses AI. Check for mistakes.
Comment on lines 48 to 61
# Listings would require parsing output commands like `snap list` and `flatpak list`
# For MVP, we just show support status
if self.snap_avail:
table.add_row("Snap Support", "Detected", "Active")
else:
table.add_row("Snap Support", "Not Found", "-")

if self.flatpak_avail:
table.add_row("Flatpak Support", "Detected", "Active")
else:
table.add_row("Flatpak Support", "Not Found", "-")

console.print(table)
console.print("[dim](Full package listing implementation pending parsing logic)[/dim]")
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The documentation comment is misleading. The list_packages method does not actually list installed packages as the docstring implies. Instead, it only shows whether Snap and Flatpak are detected on the system. The comment on line 61 acknowledges this is pending implementation, but the function name and header message suggest full functionality. Consider renaming the method to check_backends_status or similar to accurately reflect what it does.

Suggested change
# Listings would require parsing output commands like `snap list` and `flatpak list`
# For MVP, we just show support status
if self.snap_avail:
table.add_row("Snap Support", "Detected", "Active")
else:
table.add_row("Snap Support", "Not Found", "-")
if self.flatpak_avail:
table.add_row("Flatpak Support", "Detected", "Active")
else:
table.add_row("Flatpak Support", "Not Found", "-")
console.print(table)
console.print("[dim](Full package listing implementation pending parsing logic)[/dim]")
# Snap packages
if self.snap_avail:
try:
output = subprocess.check_output(["snap", "list"], text=True)
lines = output.strip().splitlines()
# First line is usually a header: "Name Version Rev ..."
for line in lines[1:]:
parts = line.split()
if len(parts) >= 2:
name = parts[0]
version = parts[1]
table.add_row(name, "snap", version)
if len(table.rows) == 0:
# No snap packages found despite snap being available
table.add_row("No Snap packages found", "snap", "-")
except (subprocess.CalledProcessError, FileNotFoundError):
table.add_row("Error listing Snap packages", "snap", "-")
else:
table.add_row("Snap Support", "Not Found", "-")
# Flatpak packages
if self.flatpak_avail:
try:
output = subprocess.check_output(
["flatpak", "list", "--columns=application,version"],
text=True,
)
lines = output.strip().splitlines()
for line in lines:
if not line.strip():
continue
# flatpak typically separates columns with tabs; fall back to whitespace
if "\t" in line:
parts = line.split("\t")
else:
parts = line.split()
if len(parts) >= 1:
name = parts[0]
version = parts[1] if len(parts) > 1 and parts[1] else "-"
table.add_row(name, "flatpak", version)
# If flatpak is available but no rows were added for it
flatpak_rows = [r for r in table.rows if len(r.cells) >= 2 and r.cells[1].plain == "flatpak"]
if not flatpak_rows:
table.add_row("No Flatpak packages found", "flatpak", "-")
except (subprocess.CalledProcessError, FileNotFoundError):
table.add_row("Error listing Flatpak packages", "flatpak", "-")
else:
table.add_row("Flatpak Support", "Not Found", "-")
console.print(table)

Copilot uses AI. Check for mistakes.
Comment on lines 78 to 88
def _get_install_cmd(self, backend: str, package: str) -> List[str]:
if backend == "snap":
return ["sudo", "snap", "install", package]
else:
return ["flatpak", "install", "-y", package]

def _get_remove_cmd(self, backend: str, package: str) -> List[str]:
if backend == "snap":
return ["sudo", "snap", "remove", package]
else:
return ["flatpak", "uninstall", "-y", package]
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

Flatpak commands may require additional parameters for system-wide vs user installation. The current implementation assumes default behavior, but flatpak typically requires specifying "--user" or "--system" scope. Without this, the commands may fail or behave inconsistently. Consider adding a parameter to specify the installation scope or defaulting to "--user" which doesn't require sudo.

Copilot uses AI. Check for mistakes.
Comment on lines 8 to 103
class UnifiedPackageManager:
"""
Unified manager for Snap and Flatpak packages.
"""
def __init__(self):
self.snap_avail = shutil.which("snap") is not None
self.flatpak_avail = shutil.which("flatpak") is not None

def check_backends(self):
if not self.snap_avail and not self.flatpak_avail:
cx_print("Warning: Neither 'snap' nor 'flatpak' found on this system.", "warning")
cx_print("Commands will run in DRY-RUN mode or fail.", "info")

def install(self, package: str, dry_run: bool = False):
self.check_backends()

backend = self._choose_backend("install")
if not backend:
return

cmd = self._get_install_cmd(backend, package)
self._run_cmd(cmd, dry_run)

def remove(self, package: str, dry_run: bool = False):
self.check_backends()

backend = self._choose_backend("remove")
if not backend:
return

cmd = self._get_remove_cmd(backend, package)
self._run_cmd(cmd, dry_run)

def list_packages(self):
cx_header("Installed Packages (Snap & Flatpak)")
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Package")
table.add_column("Backend")
table.add_column("Version", style="dim")

# Listings would require parsing output commands like `snap list` and `flatpak list`
# For MVP, we just show support status
if self.snap_avail:
table.add_row("Snap Support", "Detected", "Active")
else:
table.add_row("Snap Support", "Not Found", "-")

if self.flatpak_avail:
table.add_row("Flatpak Support", "Detected", "Active")
else:
table.add_row("Flatpak Support", "Not Found", "-")

console.print(table)
console.print("[dim](Full package listing implementation pending parsing logic)[/dim]")

def _choose_backend(self, action: str) -> Optional[str]:
if self.snap_avail and self.flatpak_avail:
return Prompt.ask(
f"Choose backend to {action}",
choices=["snap", "flatpak"],
default="snap"
)
elif self.snap_avail:
return "snap"
elif self.flatpak_avail:
return "flatpak"
else:
# Fallback for testing/dry-run if forced, or just default to snap for print
return "snap"

def _get_install_cmd(self, backend: str, package: str) -> List[str]:
if backend == "snap":
return ["sudo", "snap", "install", package]
else:
return ["flatpak", "install", "-y", package]

def _get_remove_cmd(self, backend: str, package: str) -> List[str]:
if backend == "snap":
return ["sudo", "snap", "remove", package]
else:
return ["flatpak", "uninstall", "-y", package]

def _run_cmd(self, cmd: List[str], dry_run: bool):
cmd_str = " ".join(cmd)
if dry_run:
cx_print(f"[Dry Run] would execute: [bold]{cmd_str}[/bold]", "info")
return

cx_print(f"Running: {cmd_str}...", "info")
try:
subprocess.check_call(cmd)
cx_print("Command executed successfully.", "success")
except subprocess.CalledProcessError as e:
cx_print(f"Command failed: {e}", "error")
except FileNotFoundError:
cx_print(f"Executable not found: {cmd[0]}", "error")
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The new UnifiedPackageManager class lacks test coverage. Given that the repository has comprehensive test coverage for other features (as evidenced by tests/ directory with extensive test files), this new functionality should include tests for: backend detection, command generation, install/remove operations, dry-run mode, error handling, and the list_packages method. Consider adding a test file like tests/test_package_manager.py.

Copilot uses AI. Check for mistakes.
except subprocess.CalledProcessError as e:
cx_print(f"Command failed: {e}", "error")
except FileNotFoundError:
cx_print(f"Executable not found: {cmd[0]}", "error")
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

Inconsistent indentation detected. The 'except FileNotFoundError:' block uses 5 spaces instead of the standard 4 spaces for indentation, which breaks Python's indentation consistency.

Suggested change
cx_print(f"Executable not found: {cmd[0]}", "error")
cx_print(f"Executable not found: {cmd[0]}", "error")

Copilot uses AI. Check for mistakes.
Comment on lines 22 to 38
self.check_backends()

backend = self._choose_backend("install")
if not backend:
return

cmd = self._get_install_cmd(backend, package)
self._run_cmd(cmd, dry_run)

def remove(self, package: str, dry_run: bool = False):
self.check_backends()

backend = self._choose_backend("remove")
if not backend:
return

cmd = self._get_remove_cmd(backend, package)
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

Code duplication in install and remove methods. Both methods have identical structure: check_backends, choose_backend, get command, run command. Consider extracting this common pattern into a single method like _execute_action(action_type, package, dry_run) to improve maintainability and reduce duplication.

Suggested change
self.check_backends()
backend = self._choose_backend("install")
if not backend:
return
cmd = self._get_install_cmd(backend, package)
self._run_cmd(cmd, dry_run)
def remove(self, package: str, dry_run: bool = False):
self.check_backends()
backend = self._choose_backend("remove")
if not backend:
return
cmd = self._get_remove_cmd(backend, package)
self._execute_action("install", package, dry_run)
def remove(self, package: str, dry_run: bool = False):
self._execute_action("remove", package, dry_run)
def _execute_action(self, action: str, package: str, dry_run: bool = False):
"""
Execute a package management action (e.g., install, remove) using the
selected backend, honoring dry-run mode.
"""
self.check_backends()
backend = self._choose_backend(action)
if not backend:
return
if action == "install":
cmd = self._get_install_cmd(backend, package)
elif action == "remove":
cmd = self._get_remove_cmd(backend, package)
else:
raise ValueError(f"Unsupported action: {action}")

Copilot uses AI. Check for mistakes.
cortex/cli.py Outdated
pkg_remove.add_argument("--dry-run", action="store_true")

# pkg list
pkg_subs.add_parser("list", help="List packages")
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The help text for the "list" subcommand is misleading. It says "List packages" but based on the implementation in package_manager.py (line 41-61), this command only shows backend support status, not actual installed packages. The help text should be updated to something like "Show backend status" or "Check Snap/Flatpak availability" to accurately reflect what the command does.

Suggested change
pkg_subs.add_parser("list", help="List packages")
pkg_subs.add_parser("list", help="Show Snap/Flatpak backend status")

Copilot uses AI. Check for mistakes.

cx_print(f"Running: {cmd_str}...", "info")
try:
subprocess.check_call(cmd)
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

Missing timeout parameter in subprocess.check_call. The codebase consistently uses timeout parameters in subprocess calls (as seen in coordinator.py line 182 and many other files) to prevent hanging on unresponsive commands. This is particularly important for package manager commands which may hang waiting for network or user input. Consider using subprocess.run with a timeout parameter instead of subprocess.check_call.

Copilot uses AI. Check for mistakes.
Comment on lines 64 to 69
if self.snap_avail and self.flatpak_avail:
return Prompt.ask(
f"Choose backend to {action}",
choices=["snap", "flatpak"],
default="snap"
)
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

Interactive prompt in non-interactive contexts could cause hangs. The _choose_backend method uses Prompt.ask which blocks for user input. If this code is called in a non-interactive environment (CI/CD, scripts, etc.), it will hang indefinitely. Consider adding a non-interactive mode parameter or environment variable check to automatically select a default backend when stdin is not a TTY.

Copilot uses AI. Check for mistakes.
import shutil
import subprocess
from typing import List, Optional
from rich.prompt import Confirm, Prompt
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

Unused import detected. The Confirm class is imported from rich.prompt but never used in the code. Consider removing it to keep imports clean, or if it was intended for future use (e.g., confirming package removal), add a TODO comment explaining its planned purpose.

Suggested change
from rich.prompt import Confirm, Prompt
from rich.prompt import Prompt

Copilot uses AI. Check for mistakes.
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 (4)
cortex/cli.py (4)

1524-1544: Handle missing pkg_action gracefully.

When a user runs cortex pkg without a subcommand, pkg_action is None, causing the code to fall through to the else block with the misleading error "Unknown package manager action". Add an explicit check at the start (after line 1530) to handle this case clearly, similar to the notify command pattern (lines 110-112) and the env command pattern (lines 938-942).

🔎 Suggested fix
     def pkg(self, args: argparse.Namespace) -> int:
         """Handle package manager commands."""
         try:
             from cortex.package_manager import UnifiedPackageManager
 
             mgr = UnifiedPackageManager()
             action = getattr(args, "pkg_action", None)
+            
+            if not action:
+                self._print_error("Please specify a subcommand (install/remove/list/storage/permissions)")
+                return 1
 
             if action == "install":
                 mgr.install(args.package, dry_run=args.dry_run)

1532-1545: Validate operation success before reporting success.

The install, remove, list_packages, storage_analysis, and check_permissions methods of UnifiedPackageManager don't return values (they implicitly return None), but the handler always returns 0 for success. If these operations encounter errors handled internally (e.g., when check_backends shows warnings or commands fail), the CLI will still report success. Consider having these methods return boolean success indicators or raising exceptions on failure.

Verify whether the UnifiedPackageManager methods can fail silently:

#!/bin/bash
# Check the return types and error handling of UnifiedPackageManager methods
ast-grep --pattern $'def install($$$):
  $$$'

ast-grep --pattern $'def remove($$$):
  $$$'

ast-grep --pattern $'def list_packages($$$):
  $$$'

1582-1582: Document the "apps" alias in help output.

The "apps" alias for the "pkg" command is not documented in the help table. Line 1890 defines both "pkg" and "apps" as valid commands via aliases=["apps"], but users won't discover the alias from the help output. Consider updating the help text to "pkg/apps" to match the pattern used elsewhere.

🔎 Suggested fix
-    table.add_row("pkg", "Manage Snap/Flatpak packages")
+    table.add_row("pkg/apps", "Manage Snap/Flatpak packages")

1905-1905: Clarify the "list" subcommand help text.

The help text "List packages" is misleading. Based on the UnifiedPackageManager.list_packages() implementation (package_manager.py lines 41-61), this command shows backend support status (whether Snap/Flatpak are available), not a list of installed packages. Update the help text to accurately reflect this behavior, e.g., "Show Snap/Flatpak backend status" or "Check backend availability".

🔎 Suggested fix
-    pkg_subs.add_parser("list", help="List packages")
+    pkg_subs.add_parser("list", help="Show Snap/Flatpak backend status")
🧹 Nitpick comments (1)
cortex/cli.py (1)

1546-1548: Narrow the exception handling scope.

The broad except Exception catches all exceptions, which can mask programming errors and make debugging difficult. Consider catching specific exceptions (e.g., ImportError for the missing module, AttributeError for incorrect args) or at least log the full traceback in verbose mode similar to other commands (e.g., lines 782-786).

🔎 Suggested improvement
         except Exception as e:
             self._print_error(f"Package Manager failed: {e}")
+            if self.verbose:
+                import traceback
+                traceback.print_exc()
             return 1
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67b2498 and f4d54f3.

📒 Files selected for processing (2)
  • cortex/cli.py
  • cortex/package_manager.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • cortex/package_manager.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/package_manager.py (6)
  • UnifiedPackageManager (8-184)
  • install (27-42)
  • remove (44-59)
  • list_packages (61-82)
  • storage_analysis (84-108)
  • check_permissions (110-125)
🔇 Additional comments (1)
cortex/cli.py (1)

1960-1961: LGTM!

The command routing correctly handles both the "pkg" command and its "apps" alias by routing them to the same handler method. This is consistent with how the parser is configured (line 1890).

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.

@murataslan1 This PR doesn’t follow the contributing.md guidelines yet.
Tests, documentation, a demo video, and the AI usage section in the PR description are missing.
Marking this as draft until those are added. Please update and ping once done.

@Anshgrover23 Anshgrover23 marked this pull request as draft January 5, 2026 11:10
@murataslan1
Copy link
Contributor Author

@Anshgrover23 I have updated the PR with the required changes (Tests, Docs, Demo Video, and AI Disclosure). Ready for re-review.

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.

@murataslan1

Uploading Screen Recording 2026-01-08 at 14.31.53.mov…

Where is video ? Also address bot comments first.

@murataslan1
Copy link
Contributor Author

@Anshgrover23

here's the video: https://drive.google.com/file/d/1H6ppgTK-bp03zGDpjPGQ7R5pzzti5Aiq/view?usp=sharing

working on the lint fixes now, will push soon

- Remove deprecated typing imports (List, Optional)
- Use modern Python 3.10+ type annotations (list, X | None)
- Fix import sorting (I001)
- Remove trailing whitespace (W291, W293)
- Clean up blank lines with whitespace
@murataslan1
Copy link
Contributor Author

@Anshgrover23 I have fixed all lint errors in package_manager.py:

  • Removed deprecated typing imports (List, Optional)
    • Using modern Python 3.10+ type annotations (list, X | None)
    • Fixed import sorting (I001)
    • Cleaned up trailing whitespace
      Ready for re-review!

Resolved conflicts in cortex/cli.py by keeping both:
- Our pkg command definitions (Snap/Flatpak package manager)
- New env subcommands from main (audit, check, path management)
@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.

@murataslan1 Kindly Address coderabbit comments and CI checks are failing as well.

table.add_row("cache stats", "Show LLM cache statistics")
table.add_row("stack <name>", "Install the stack")
table.add_row("sandbox <cmd>", "Test packages in Docker sandbox")
table.add_row("pkg", "Manage Snap/Flatpak packages")
Copy link
Collaborator

Choose a reason for hiding this comment

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

@murataslan1 Please address this one as well.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@murataslan1 Why do we need a demo script ?

@sonarqubecloud
Copy link

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.

Snap/Flatpak Unified Manager

3 participants