From 9e2397da06a2512d917667e9758b5cada3933690 Mon Sep 17 00:00:00 2001 From: Krish Date: Wed, 24 Dec 2025 17:45:43 +0000 Subject: [PATCH 1/8] added script_gen.py,added test cases for script_gen, modified cli.py --- cortex/cli.py | 79 ++++++++++++ cortex/script_gen.py | 257 +++++++++++++++++++++++++++++++++++++++ tests/test_script_gen.py | 226 ++++++++++++++++++++++++++++++++++ 3 files changed, 562 insertions(+) create mode 100644 cortex/script_gen.py create mode 100644 tests/test_script_gen.py diff --git a/cortex/cli.py b/cortex/cli.py index c808d5e4..52149b5a 100644 --- a/cortex/cli.py +++ b/cortex/cli.py @@ -294,6 +294,38 @@ def doctor(self): doctor = SystemDoctor() return doctor.run_checks() + def script(self, args: argparse.Namespace) -> int: + """Handle cortex script commands""" + try: + from cortex.script_gen import ScriptGenerator + + generator = ScriptGenerator() + + if args.script_action == "generate": + generator.generate( + filename=args.filename, + stack=args.stack, + format=args.format, + dry_run=args.dry_run, + ) + return 0 + + elif args.script_action == "test": + generator.test(filename=args.filename, sandbox=args.sandbox) + return 0 + + elif args.script_action == "history": + limit = args.limit if hasattr(args, "limit") else 10 + generator.show_history(limit=limit) + return 0 + + except FileNotFoundError: + console.print(f"[red]✗ Script file not found: {args.filename}[/red]") + return 1 + except Exception as e: + console.print(f"[red]✗ Script command failed: {str(e)}[/red]") + return 1 + def install( self, software: str, @@ -774,6 +806,11 @@ def show_rich_help(): table.add_row("cache stats", "Show LLM cache statistics") table.add_row("stack ", "Install the stack") table.add_row("doctor", "System health check") + table.add_row( + "script generate --stack ", "Generate installation scripts" + ) + table.add_row("script test ", "Validate script syntax") + table.add_row("script history", "View generation history") console.print(table) console.print() @@ -830,6 +867,46 @@ def main(): # doctor command doctor_parser = subparsers.add_parser("doctor", help="Run system health check") + # script generator command + script_parser = subparsers.add_parser( + "script", help="Generate installation scripts", aliases=["scripts"] + ) + script_subs = script_parser.add_subparsers(dest="script_action", required=True) + + # cortex script generate + generate_parser = script_subs.add_parser("generate", help="Generate installation script") + generate_parser.add_argument("filename", help="Output script filename (e.g., docker-setup.sh)") + generate_parser.add_argument( + "--stack", + "-s", + default="docker", + choices=["docker", "python", "nodejs", "ollama"], + help="Stack to install (default: docker)", + ) + generate_parser.add_argument( + "--format", + "-f", + default="bash", + choices=["bash", "ansible"], + help="Output format (default: bash)", + ) + generate_parser.add_argument( + "--dry-run", action="store_true", help="Preview script without writing to file" + ) + + # cortex script test + test_parser = script_subs.add_parser("test", help="Test generated script syntax") + test_parser.add_argument("filename", help="Script file to validate") + test_parser.add_argument( + "--sandbox", action="store_true", default=True, help="Run in sandbox mode (default: True)" + ) + + # cortex script history + history_parser = script_subs.add_parser("history", help="Show script generation history") + history_parser.add_argument( + "--limit", "-l", type=int, default=10, help="Number of entries to show (default: 10)" + ) + # Install command install_parser = subparsers.add_parser("install", help="Install software") install_parser.add_argument("software", type=str, help="Software to install") @@ -934,6 +1011,8 @@ def main(): return cli.stack(args) elif args.command == "doctor": return cli.doctor() + elif args.command == "script": + return cli.script(args) elif args.command == "cache": if getattr(args, "cache_action", None) == "stats": return cli.cache_stats() diff --git a/cortex/script_gen.py b/cortex/script_gen.py new file mode 100644 index 00000000..b4de2243 --- /dev/null +++ b/cortex/script_gen.py @@ -0,0 +1,257 @@ +""" +Generates standalone installation scripts for offline or automated deployments. +""" + +import subprocess +import sys +from datetime import datetime +from pathlib import Path + +from rich.console import Console + +console = Console() + +STACK_DEPS = { + "docker": { + "packages": ["docker.io", "docker-compose"], + "check_command": "docker", + "verification": "docker --version", + }, + "python": { + "packages": ["python3.11", "python3.11-venv"], + "check_command": "python3.11", + "verification": "python3.11 --version", + }, + "nodejs": { + "packages": ["nodejs", "npm"], + "check_command": "node", + "verification": "node --version", + }, + "ollama": { + "packages": ["ollama"], + "check_command": "ollama", + "verification": "ollama --version", + }, +} + +BASH_TEMPLATE = """#!/bin/bash +# Generated by Cortex Linux - {date} +# Stack: {stack} + +set -euo pipefail + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +log_info() {{ echo -e "${{GREEN}}✓${{NC}} $1"; }} +log_warn() {{ echo -e "${{YELLOW}}⚠${{NC}} $1"; }} +log_error() {{ echo -e "${{RED}}✗${{NC}} $1"; }} + +command_exists() {{ + command -v "$1" >/dev/null 2>&1 +}} + +trap 'log_error "Failed at line $LINENO"' ERR + +log_info "Installing {stack}..." + +if ! sudo apt-get update >/dev/null 2>&1; then + log_error "Failed to update package manager" + exit 1 +fi + +{install_commands} + +log_info "Verifying {stack}..." +{verify_commands} +log_info "Complete!" +""" + + +class ScriptGenerator: + def __init__(self): + self.cortex_dir = Path.home() / "cortex" / "install-scripts" + self.cortex_dir.mkdir(parents=True, exist_ok=True) + self.history_file = self.cortex_dir / "script_history.yaml" + + def generate( + self, filename: str, stack: str = "docker", format: str = "bash", dry_run: bool = False + ): + if stack not in STACK_DEPS: + console.print(f"[red]✗ Unknown stack: {stack}[/red]") + console.print(f"Available stacks: {', '.join(STACK_DEPS.keys())}") + sys.exit(1) + + deps = STACK_DEPS[stack] + + if format == "bash": + install_cmds = [] + for pkg in deps.get("packages", []): + check_command = deps.get("check_command", pkg) + install_cmds.append( + f"""if ! command_exists {check_command}; then + log_info "Installing {pkg}..." + sudo apt-get install -y {pkg} >/dev/null 2>&1 + else + log_warn "{pkg} already installed" + fi""" + ) + + content = BASH_TEMPLATE.format( + date=datetime.now().isoformat(), + stack=stack, + install_commands="\n".join(install_cmds), + verify_commands=deps.get("verification", ""), + ) + elif format == "ansible": + pkg_list = "\n".join([f" - {pkg}" for pkg in deps.get("packages", [])]) + content = f"""--- +# Generated by Cortex Linux - {datetime.now().isoformat()} +# Stack: {stack} +- name: Install {stack} + hosts: localhost + gather_facts: yes + become: yes + tasks: + - name: Update apt cache + apt: + update_cache: yes + when: ansible_os_family == "Debian" + - name: Install packages + apt: + pkg: +{pkg_list} + state: present + when: ansible_os_family == "Debian" + - name: Verify installation + command: {deps.get("verification", "echo OK")} + register: version_check + changed_when: false +""" + else: + console.print(f"[red]✗ Unknown format: {format}[/red]") + sys.exit(1) + + if filename.startswith("/"): + path = Path(filename) + else: + path = self.cortex_dir / filename + + if dry_run: + console.print("[yellow]📋 DRY RUN - Script content:[/yellow]") + console.print("[dim]" + content + "[/dim]") + console.print(f"[yellow]Would write to: {path.absolute()}[/yellow]") + else: + try: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content) + path.chmod(0o755) + + if not path.exists(): + console.print(f"[red]✗ Failed to write script to {path.absolute()}[/red]") + sys.exit(1) + + console.print("[green]✓ Generated installation script[/green]") + console.print(f"[dim]Location: {path.absolute()}[/dim]") + console.print(f"[dim]Run: bash {path.absolute()}[/dim]") + + self._save_to_history(stack, format, str(path.absolute())) + except Exception as e: + console.print(f"[red]✗ Error writing script: {str(e)}[/red]") + sys.exit(1) + + def test(self, filename: str, sandbox: bool = True): + if filename.startswith("/"): + path = Path(filename) + else: + path = self.cortex_dir / filename + + if not path.exists(): + console.print(f"[red]✗ Script not found: {filename}[/red]") + console.print(f"[dim]Expected: {path.absolute()}[/dim]") + sys.exit(1) + + console.print(f"[cyan]🧪 Testing script: {path.absolute()}[/cyan]") + + try: + if sandbox: + result = subprocess.run( + ["bash", "-n", str(path)], capture_output=True, text=True, timeout=10 + ) + if result.returncode != 0: + console.print("[red]✗ Syntax error in script:[/red]") + console.print(f"[dim]{result.stderr}[/dim]") + sys.exit(1) + + console.print("[green]✓ Syntax check passed[/green]") + console.print("[green]✓ Script is valid bash[/green]") + console.print("[green]✓ Ready for execution[/green]") + else: + console.print("[green]✓ Syntax validation passed[/green]") + + except subprocess.TimeoutExpired: + console.print("[red]✗ Script test timed out[/red]") + sys.exit(1) + except Exception as e: + console.print(f"[red]✗ Test error: {str(e)}[/red]") + sys.exit(1) + + def _save_to_history(self, stack: str, format: str, filename: str) -> None: + try: + import yaml + except ImportError: + return + + entry = { + "timestamp": datetime.now().isoformat(), + "stack": stack, + "format": format, + "filename": filename, + } + + history = [] + if self.history_file.exists(): + try: + content = self.history_file.read_text() + if content.strip(): + history = yaml.safe_load(content) or [] + except Exception: + history = [] + + history.append(entry) + self.history_file.write_text(yaml.safe_dump(history, sort_keys=False)) + + def show_history(self, limit: int = 10) -> None: + try: + import yaml + except ImportError: + console.print("[yellow]⚠ YAML not available, cannot show history[/yellow]") + return + + if not self.history_file.exists(): + console.print("[yellow]⚠ No script history found[/yellow]") + return + + try: + data = yaml.safe_load(self.history_file.read_text()) or [] + except Exception: + console.print("[yellow]⚠ Error reading history[/yellow]") + return + + if not data: + console.print("[yellow]⚠ Script history is empty[/yellow]") + return + + console.print("[bold]📜 Script Generation History (latest first)[/bold]") + console.print() + + for i, entry in enumerate(reversed(data[-limit:]), 1): + timestamp = entry.get("timestamp", "?") + stack = entry.get("stack", "?") + fmt = entry.get("format", "?") + filename = entry.get("filename", "?") + + console.print(f"{i}. [cyan]{stack}[/cyan] ({fmt}) → {filename}") + console.print(f" [dim]{timestamp}[/dim]") diff --git a/tests/test_script_gen.py b/tests/test_script_gen.py new file mode 100644 index 00000000..6d3da201 --- /dev/null +++ b/tests/test_script_gen.py @@ -0,0 +1,226 @@ +""" +Complete test suite for cortex script generator +""" + +from pathlib import Path + +import pytest + +from cortex.script_gen import STACK_DEPS, ScriptGenerator + + +class TestScriptGenerator: + """Test suite for ScriptGenerator class""" + + @pytest.fixture + def generator(self): + """Create ScriptGenerator instance for each test""" + return ScriptGenerator() + + @pytest.fixture + def temp_script(self, tmp_path): + """Create temporary script file""" + script_file = tmp_path / "test_script.sh" + return script_file + + # ===== GENERATION TESTS ===== + + def test_generate_docker_bash(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker", format="bash") + assert temp_script.exists() + content = temp_script.read_text() + assert "#!/bin/bash" in content + assert "docker.io" in content + assert "docker-compose" in content + assert "set -euo pipefail" in content + + def test_generate_python_bash(self, generator, temp_script): + generator.generate(str(temp_script), stack="python", format="bash") + assert temp_script.exists() + content = temp_script.read_text() + assert "python3.11" in content + assert "python3.11-venv" in content + + def test_generate_nodejs_bash(self, generator, temp_script): + generator.generate(str(temp_script), stack="nodejs", format="bash") + assert temp_script.exists() + content = temp_script.read_text() + assert "nodejs" in content + assert "npm" in content + + def test_generate_ollama_bash(self, generator, temp_script): + generator.generate(str(temp_script), stack="ollama", format="bash") + assert temp_script.exists() + content = temp_script.read_text() + assert "ollama" in content + + def test_generate_docker_ansible(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker", format="ansible") + assert temp_script.exists() + content = temp_script.read_text() + assert "---" in content + assert "name: Install docker" in content + assert "docker.io" in content + assert "docker-compose" in content + + def test_generate_python_ansible(self, generator, temp_script): + generator.generate(str(temp_script), stack="python", format="ansible") + assert temp_script.exists() + content = temp_script.read_text() + assert "---" in content + assert "python3.11" in content + + # ===== FILE OPERATIONS ===== + + def test_file_is_executable(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker", format="bash") + assert temp_script.stat().st_mode & 0o111 # Check execute bit + + def test_file_contains_timestamp(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker", format="bash") + content = temp_script.read_text() + assert "Generated by Cortex Linux" in content + assert "T" in content # ISO format timestamp + + def test_dry_run_no_write(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker", dry_run=True) + assert not temp_script.exists() + + # ===== ERROR HANDLING ===== + + def test_invalid_stack(self, generator, temp_script): + with pytest.raises(SystemExit): + generator.generate(str(temp_script), stack="invalid_stack") + + def test_invalid_format(self, generator, temp_script): + with pytest.raises(SystemExit): + generator.generate(str(temp_script), stack="docker", format="invalid_format") + + # ===== IDEMPOTENCY TESTS ===== + def test_idempotent_check_docker(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker") + content = temp_script.read_text() + + # Check uses 'docker' (check_command), installs 'docker.io' (package) + assert "command_exists docker" in content + assert "Installing docker.io" in content + assert "Installing docker-compose" in content + assert temp_script.stat().st_mode & 0o111 # executable + + def test_idempotent_check_python(self, generator, temp_script): + generator.generate(str(temp_script), stack="python") + content = temp_script.read_text() + + assert "command_exists python3.11" in content + assert "Installing python3.11" in content + assert temp_script.stat().st_mode & 0o111 + + def test_idempotent_check_nodejs(self, generator, temp_script): + generator.generate(str(temp_script), stack="nodejs") + content = temp_script.read_text() + + # Check uses 'node' (check_command), installs 'nodejs' (package) + assert "command_exists node" in content + assert "Installing nodejs" in content + assert "Installing npm" in content + assert temp_script.stat().st_mode & 0o111 + + def test_idempotent_check_ollama(self, generator, temp_script): + generator.generate(str(temp_script), stack="ollama") + content = temp_script.read_text() + + assert "command_exists ollama" in content + assert "Installing ollama" in content + assert temp_script.stat().st_mode & 0o111 + + # ===== BASH BEST PRACTICES ===== + def test_strict_mode_bash(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker") + content = temp_script.read_text() + assert "set -euo pipefail" in content + + def test_error_trap_bash(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker") + content = temp_script.read_text() + assert "trap" in content + assert "log_error" in content + + def test_logging_functions_bash(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker") + content = temp_script.read_text() + assert "log_info()" in content + assert "log_warn()" in content + assert "log_error()" in content + + def test_color_codes_bash(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker") + content = temp_script.read_text() + assert "GREEN=" in content + assert "YELLOW=" in content + assert "RED=" in content + + # ===== TESTING MODE ===== + def test_syntax_check_valid(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker") + # Should not raise exception + generator.test(str(temp_script), sandbox=True) + + def test_syntax_check_missing_file(self, generator, temp_script): + with pytest.raises(SystemExit): + generator.test("non_existent_file.sh") + + # ===== TEMPLATE VERIFICATION ===== + def test_verification_command_docker(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker") + content = temp_script.read_text() + assert "docker --version" in content + + def test_verification_command_python(self, generator, temp_script): + generator.generate(str(temp_script), stack="python") + content = temp_script.read_text() + assert "python3.11 --version" in content + + def test_verification_command_nodejs(self, generator, temp_script): + generator.generate(str(temp_script), stack="nodejs") + content = temp_script.read_text() + assert "node --version" in content + + def test_verification_command_ollama(self, generator, temp_script): + generator.generate(str(temp_script), stack="ollama") + content = temp_script.read_text() + assert "ollama --version" in content + + # ===== ANSIBLE SPECIFIC ===== + def test_ansible_yaml_header(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker", format="ansible") + content = temp_script.read_text() + assert content.startswith("---") + + def test_ansible_hosts_localhost(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker", format="ansible") + content = temp_script.read_text() + assert "hosts: localhost" in content + + def test_ansible_become(self, generator, temp_script): + generator.generate(str(temp_script), stack="docker", format="ansible") + content = temp_script.read_text() + assert "become: yes" in content + + # ===== MULTI-STACK COVERAGE ===== + @pytest.mark.parametrize("stack", ["docker", "python", "nodejs", "ollama"]) + def test_all_stacks_bash(self, generator, temp_script, stack): + generator.generate(str(temp_script), stack=stack, format="bash") + assert temp_script.exists() + content = temp_script.read_text() + assert f"Stack: {stack}" in content + + @pytest.mark.parametrize("stack", ["docker", "python", "nodejs", "ollama"]) + def test_all_stacks_ansible(self, generator, temp_script, stack): + generator.generate(str(temp_script), stack=stack, format="ansible") + assert temp_script.exists() + content = temp_script.read_text() + assert f"Install {stack}" in content + + +if __name__ == "__main__": + pytest.main([__file__, "-v", "--tb=short"]) From e38b665895040fe619978c35b095e020a068dc44 Mon Sep 17 00:00:00 2001 From: Krish Date: Thu, 25 Dec 2025 04:53:40 +0000 Subject: [PATCH 2/8] Updated code according to feedback --- cortex/cli.py | 2 +- cortex/script_gen.py | 57 ++++++++++++++++++++++++++++------------ tests/test_script_gen.py | 12 ++++----- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/cortex/cli.py b/cortex/cli.py index 443186ea..b7072a10 100644 --- a/cortex/cli.py +++ b/cortex/cli.py @@ -316,7 +316,7 @@ def script(self, args: argparse.Namespace) -> int: return 0 elif args.script_action == "history": - limit = args.limit if hasattr(args, "limit") else 10 + limit = args.limit generator.show_history(limit=limit) return 0 diff --git a/cortex/script_gen.py b/cortex/script_gen.py index b4de2243..062fe208 100644 --- a/cortex/script_gen.py +++ b/cortex/script_gen.py @@ -6,30 +6,44 @@ import sys from datetime import datetime from pathlib import Path +from typing import TypedDict from rich.console import Console console = Console() -STACK_DEPS = { + +class StackConfig(TypedDict): + packages: list[dict | str] + verification: str + + +STACK_DEPS: dict[str, StackConfig] = { "docker": { - "packages": ["docker.io", "docker-compose"], - "check_command": "docker", + "packages": [ + {"name": "docker.io", "check_command": "docker"}, + {"name": "docker-compose", "check_command": "docker-compose"}, + ], "verification": "docker --version", }, "python": { - "packages": ["python3.11", "python3.11-venv"], - "check_command": "python3.11", - "verification": "python3.11 --version", + "packages": [ + {"name": "python3", "check_command": "python3"}, + {"name": "python3-venv", "check_command": "python3"}, + ], + "verification": "python3 --version", }, "nodejs": { - "packages": ["nodejs", "npm"], - "check_command": "node", + "packages": [ + {"name": "nodejs", "check_command": "node"}, + {"name": "npm", "check_command": "npm"}, + ], "verification": "node --version", }, "ollama": { - "packages": ["ollama"], - "check_command": "ollama", + "packages": [ + {"name": "ollama", "check_command": "ollama"}, + ], "verification": "ollama --version", }, } @@ -71,7 +85,10 @@ class ScriptGenerator: - def __init__(self): + packages: list[dict | str] + verification: str + + def __init__(self) -> None: self.cortex_dir = Path.home() / "cortex" / "install-scripts" self.cortex_dir.mkdir(parents=True, exist_ok=True) self.history_file = self.cortex_dir / "script_history.yaml" @@ -89,14 +106,20 @@ def generate( if format == "bash": install_cmds = [] for pkg in deps.get("packages", []): - check_command = deps.get("check_command", pkg) + if isinstance(pkg, dict): + pkg_name = pkg["name"] + check_command = pkg["check_command"] + else: + pkg_name = pkg + check_command = pkg + install_cmds.append( f"""if ! command_exists {check_command}; then - log_info "Installing {pkg}..." - sudo apt-get install -y {pkg} >/dev/null 2>&1 - else - log_warn "{pkg} already installed" - fi""" + log_info "Installing {pkg_name}..." + sudo apt-get install -y {pkg_name} >/dev/null 2>&1 + else + log_warn "{pkg_name} already installed" + fi""" ) content = BASH_TEMPLATE.format( diff --git a/tests/test_script_gen.py b/tests/test_script_gen.py index 6d3da201..265c4f49 100644 --- a/tests/test_script_gen.py +++ b/tests/test_script_gen.py @@ -38,8 +38,8 @@ def test_generate_python_bash(self, generator, temp_script): generator.generate(str(temp_script), stack="python", format="bash") assert temp_script.exists() content = temp_script.read_text() - assert "python3.11" in content - assert "python3.11-venv" in content + assert "python3" in content + assert "python3 --version" in content def test_generate_nodejs_bash(self, generator, temp_script): generator.generate(str(temp_script), stack="nodejs", format="bash") @@ -68,7 +68,7 @@ def test_generate_python_ansible(self, generator, temp_script): assert temp_script.exists() content = temp_script.read_text() assert "---" in content - assert "python3.11" in content + assert "python3" in content # ===== FILE OPERATIONS ===== @@ -111,8 +111,8 @@ def test_idempotent_check_python(self, generator, temp_script): generator.generate(str(temp_script), stack="python") content = temp_script.read_text() - assert "command_exists python3.11" in content - assert "Installing python3.11" in content + assert "command_exists python3" in content + assert "Installing python3" in content assert temp_script.stat().st_mode & 0o111 def test_idempotent_check_nodejs(self, generator, temp_script): @@ -178,7 +178,7 @@ def test_verification_command_docker(self, generator, temp_script): def test_verification_command_python(self, generator, temp_script): generator.generate(str(temp_script), stack="python") content = temp_script.read_text() - assert "python3.11 --version" in content + assert "python3 --version" in content def test_verification_command_nodejs(self, generator, temp_script): generator.generate(str(temp_script), stack="nodejs") From 09a353d57581ca56a8836f8c6764edebbf665249 Mon Sep 17 00:00:00 2001 From: Krish Date: Thu, 25 Dec 2025 04:58:18 +0000 Subject: [PATCH 3/8] black-cli.py --- cortex/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cortex/cli.py b/cortex/cli.py index b7072a10..c24d046b 100644 --- a/cortex/cli.py +++ b/cortex/cli.py @@ -325,6 +325,7 @@ def script(self, args: argparse.Namespace) -> int: return 1 except Exception as e: console.print(f"[red]✗ Script command failed: {str(e)}[/red]") + def ask(self, question: str) -> int: """Answer a natural language question about the system.""" api_key = self._get_api_key() From ea9f3c594a02d5d72c8acc03377ad56b179ed154 Mon Sep 17 00:00:00 2001 From: Krish Date: Thu, 25 Dec 2025 06:11:11 +0000 Subject: [PATCH 4/8] updated code acc to feedback --- cortex/cli.py | 4 ++- cortex/script_gen.py | 73 ++++++++++++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/cortex/cli.py b/cortex/cli.py index c24d046b..c5163c20 100644 --- a/cortex/cli.py +++ b/cortex/cli.py @@ -321,10 +321,12 @@ def script(self, args: argparse.Namespace) -> int: return 0 except FileNotFoundError: - console.print(f"[red]✗ Script file not found: {args.filename}[/red]") + filename = getattr(args, "filename", "unknown") + console.print(f"[red]✗ Script file not found: {filename}[/red]") return 1 except Exception as e: console.print(f"[red]✗ Script command failed: {str(e)}[/red]") + return 1 def ask(self, question: str) -> int: """Answer a natural language question about the system.""" diff --git a/cortex/script_gen.py b/cortex/script_gen.py index 062fe208..c1a4bed0 100644 --- a/cortex/script_gen.py +++ b/cortex/script_gen.py @@ -15,7 +15,7 @@ class StackConfig(TypedDict): packages: list[dict | str] - verification: str + verification: str | None STACK_DEPS: dict[str, StackConfig] = { @@ -48,16 +48,17 @@ class StackConfig(TypedDict): }, } + BASH_TEMPLATE = """#!/bin/bash # Generated by Cortex Linux - {date} # Stack: {stack} set -euo pipefail -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' +GREEN='\\033[0;32m' +YELLOW='\\033[1;33m' +RED='\\033[0;31m' +NC='\\033[0m' log_info() {{ echo -e "${{GREEN}}✓${{NC}} $1"; }} log_warn() {{ echo -e "${{YELLOW}}⚠${{NC}} $1"; }} @@ -79,14 +80,16 @@ class StackConfig(TypedDict): {install_commands} log_info "Verifying {stack}..." -{verify_commands} +if ! {verify_commands}; then + log_error "Verification failed for {stack}" + exit 1 +fi log_info "Complete!" """ class ScriptGenerator: - packages: list[dict | str] - verification: str + """Build installation scripts for supported stacks.""" def __init__(self) -> None: self.cortex_dir = Path.home() / "cortex" / "install-scripts" @@ -95,7 +98,17 @@ def __init__(self) -> None: def generate( self, filename: str, stack: str = "docker", format: str = "bash", dry_run: bool = False - ): + ) -> None: + """ + Generate an installation script for the specified stack. + Args: + filename: Output script filename (absolute or relative to cortex_dir) + stack: Stack to install (docker, python, nodejs, ollama) + format: Output format (bash or ansible) + dry_run: If True, print content without writing file + Raises: + SystemExit: If stack or format is invalid, or file write fails + """ if stack not in STACK_DEPS: console.print(f"[red]✗ Unknown stack: {stack}[/red]") console.print(f"Available stacks: {', '.join(STACK_DEPS.keys())}") @@ -104,32 +117,42 @@ def generate( deps = STACK_DEPS[stack] if format == "bash": - install_cmds = [] + install_cmds: list[str] = [] for pkg in deps.get("packages", []): if isinstance(pkg, dict): pkg_name = pkg["name"] - check_command = pkg["check_command"] + check_command = pkg.get("check_command", pkg_name) else: pkg_name = pkg check_command = pkg install_cmds.append( f"""if ! command_exists {check_command}; then - log_info "Installing {pkg_name}..." - sudo apt-get install -y {pkg_name} >/dev/null 2>&1 - else - log_warn "{pkg_name} already installed" - fi""" + log_info "Installing {pkg_name}..." + sudo apt-get install -y {pkg_name} >/dev/null 2>&1 +else + log_warn "{pkg_name} already installed" +fi""" ) + verify_commands = deps.get("verification", "true") content = BASH_TEMPLATE.format( date=datetime.now().isoformat(), stack=stack, install_commands="\n".join(install_cmds), - verify_commands=deps.get("verification", ""), + verify_commands=verify_commands, ) + elif format == "ansible": - pkg_list = "\n".join([f" - {pkg}" for pkg in deps.get("packages", [])]) + pkg_lines: list[str] = [] + for pkg in deps.get("packages", []): + if isinstance(pkg, dict): + pkg_name = pkg.get("name") + else: + pkg_name = pkg + pkg_lines.append(f" - {pkg_name}") + pkg_list = "\n".join(pkg_lines) + content = f"""--- # Generated by Cortex Linux - {datetime.now().isoformat()} # Stack: {stack} @@ -185,7 +208,15 @@ def generate( console.print(f"[red]✗ Error writing script: {str(e)}[/red]") sys.exit(1) - def test(self, filename: str, sandbox: bool = True): + def test(self, filename: str, sandbox: bool = True) -> None: + """ + Validate the syntax of a generated script. + Args: + filename: Script file to test (absolute or relative to cortex_dir) + sandbox: If True, run bash -n syntax check; if False, skip validation + Raises: + SystemExit: If script not found or syntax check fails + """ if filename.startswith("/"): path = Path(filename) else: @@ -222,6 +253,7 @@ def test(self, filename: str, sandbox: bool = True): sys.exit(1) def _save_to_history(self, stack: str, format: str, filename: str) -> None: + """Save generation entry to history.""" try: import yaml except ImportError: @@ -234,7 +266,7 @@ def _save_to_history(self, stack: str, format: str, filename: str) -> None: "filename": filename, } - history = [] + history: list[dict] = [] if self.history_file.exists(): try: content = self.history_file.read_text() @@ -247,6 +279,7 @@ def _save_to_history(self, stack: str, format: str, filename: str) -> None: self.history_file.write_text(yaml.safe_dump(history, sort_keys=False)) def show_history(self, limit: int = 10) -> None: + """Print recent script generation history.""" try: import yaml except ImportError: From f49671cbc87cee7c042dc08095e95949a86f71fa Mon Sep 17 00:00:00 2001 From: Krish Date: Thu, 25 Dec 2025 06:39:38 +0000 Subject: [PATCH 5/8] added docstrigs, updated script_gen.py --- cortex/script_gen.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cortex/script_gen.py b/cortex/script_gen.py index c1a4bed0..53b43052 100644 --- a/cortex/script_gen.py +++ b/cortex/script_gen.py @@ -29,7 +29,7 @@ class StackConfig(TypedDict): "python": { "packages": [ {"name": "python3", "check_command": "python3"}, - {"name": "python3-venv", "check_command": "python3"}, + {"name": "python3-venv", "check_command": "python3 -m venv --help"}, ], "verification": "python3 --version", }, @@ -92,6 +92,12 @@ class ScriptGenerator: """Build installation scripts for supported stacks.""" def __init__(self) -> None: + """ + Initialize the ScriptGenerator. + + Sets up the output directory at ~/cortex/install-scripts and prepares + the history tracking file. + """ self.cortex_dir = Path.home() / "cortex" / "install-scripts" self.cortex_dir.mkdir(parents=True, exist_ok=True) self.history_file = self.cortex_dir / "script_history.yaml" @@ -279,7 +285,12 @@ def _save_to_history(self, stack: str, format: str, filename: str) -> None: self.history_file.write_text(yaml.safe_dump(history, sort_keys=False)) def show_history(self, limit: int = 10) -> None: - """Print recent script generation history.""" + """ + Display recent script generation history. + + Args: + limit: Maximum number of history entries to show (default: 10) + """ try: import yaml except ImportError: From bc8eda2ee6a8904941cd65761ea11ef9acc1a834 Mon Sep 17 00:00:00 2001 From: Krish Date: Sat, 27 Dec 2025 16:12:39 +0000 Subject: [PATCH 6/8] fix(cli): make sandbox toggleable via --no-sandbox --- cortex/cli.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cortex/cli.py b/cortex/cli.py index 65ce883b..b5b1a5c8 100644 --- a/cortex/cli.py +++ b/cortex/cli.py @@ -1245,8 +1245,12 @@ def main(): test_parser = script_subs.add_parser("test", help="Test generated script syntax") test_parser.add_argument("filename", help="Script file to validate") test_parser.add_argument( - "--sandbox", action="store_true", default=True, help="Run in sandbox mode (default: True)" + "--no-sandbox", + dest="sandbox", + action="store_false", + help="Disable sandbox mode (sandbox is enabled by default)", ) + test_parser.set_defaults(sandbox=True) # cortex script history history_parser = script_subs.add_parser("history", help="Show script generation history") From 0e81396fb4cc5810f2a3e5f57d79c6d611fabaf5 Mon Sep 17 00:00:00 2001 From: Krish Date: Sat, 27 Dec 2025 16:22:14 +0000 Subject: [PATCH 7/8] Added docstring and return type hint, which was forgotten in other pr --- cortex/cli.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cortex/cli.py b/cortex/cli.py index b5b1a5c8..30611e26 100644 --- a/cortex/cli.py +++ b/cortex/cli.py @@ -282,7 +282,12 @@ def _handle_stack_real_install(self, stack: dict[str, Any], packages: list[str]) return 0 # Run system health checks - def doctor(self): + def doctor(self) -> int: + """Run system health checks and diagnostics. + + Returns: + Exit code: 0 if healthy, 1 if warnings, 2 if failures + """ from cortex.doctor import SystemDoctor doctor = SystemDoctor() From 555865a47416df7067d7710b451c9859a47ab46a Mon Sep 17 00:00:00 2001 From: Krish Date: Wed, 31 Dec 2025 13:20:11 +0000 Subject: [PATCH 8/8] Add fallback return to satisfy return type --- cortex/cli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cortex/cli.py b/cortex/cli.py index 81b6e625..ee9e54ea 100644 --- a/cortex/cli.py +++ b/cortex/cli.py @@ -322,6 +322,9 @@ def script(self, args: argparse.Namespace) -> int: limit = args.limit generator.show_history(limit=limit) return 0 + else: + console.print(f"[red]✗ Unknown script action: {args.script_action}[/red]") + return 1 except FileNotFoundError: filename = getattr(args, "filename", "unknown")