diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 6398cc7d..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: "CodeQL Security Analysis" - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - schedule: - # Run every Monday at 6:00 AM UTC - - cron: '0 6 * * 1' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: ${{ matrix.language }} - # Use default queries plus security-extended - queries: security-extended - - - name: Autobuild - uses: github/codeql-action/autobuild@v4 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - with: - category: "/language:${{ matrix.language }}" diff --git a/cortex/cli.py b/cortex/cli.py index 17004c68..fdfcb058 100644 --- a/cortex/cli.py +++ b/cortex/cli.py @@ -550,6 +550,20 @@ def show_rich_help(): console.print("[dim]Learn more: https://cortexlinux.com/docs[/dim]") +def shell_suggest(text: str) -> int: + """ + Internal helper used by shell hotkey integration. + Prints a single suggested command to stdout. + """ + try: + from cortex.shell_integration import suggest_command + suggestion = suggest_command(text) + if suggestion: + print(suggestion) + return 0 + except Exception: + return 1 + def main(): parser = argparse.ArgumentParser( prog='cortex', @@ -655,5 +669,7 @@ def main(): print(f"❌ Unexpected error: {e}", file=sys.stderr) return 1 + + if __name__ == '__main__': sys.exit(main()) \ No newline at end of file diff --git a/cortex/shell_installer.py b/cortex/shell_installer.py new file mode 100644 index 00000000..f4e67f71 --- /dev/null +++ b/cortex/shell_installer.py @@ -0,0 +1,49 @@ +import os +from pathlib import Path + +BASH_MARKER = "# >>> cortex shell integration >>>" +ZSH_MARKER = "# >>> cortex shell integration >>>" + + +def _append_if_missing(rc_path: Path, block: str) -> bool: + if rc_path.exists(): + content = rc_path.read_text() + if BASH_MARKER in content: + return False + else: + rc_path.touch() + + with rc_path.open("a", encoding="utf-8") as f: + f.write("\n" + block + "\n") + + return True + + +def install_shell_integration() -> str: + shell = os.environ.get("SHELL", "") + home = Path.home() + + if shell.endswith("bash"): + rc = home / ".bashrc" + script_path = Path(__file__).resolve().parent.parent / "scripts" / "cortex_bash.sh" + block = f"""{BASH_MARKER} +source "{script_path}" +# <<< cortex shell integration <<< +""" + installed = _append_if_missing(rc, block) + return "bash", installed + + elif shell.endswith("zsh"): + rc = home / ".zshrc" + script_path = Path(__file__).resolve().parent.parent / "scripts" / "cortex_zsh.zsh" + block = f"""{ZSH_MARKER} +source "{script_path}" +# <<< cortex shell integration <<< +""" + installed = _append_if_missing(rc, block) + return "zsh", installed + + else: + raise RuntimeError("Unsupported shell. Only bash and zsh are supported.") + + \ No newline at end of file diff --git a/cortex/shell_integration.py b/cortex/shell_integration.py new file mode 100644 index 00000000..3f874f19 --- /dev/null +++ b/cortex/shell_integration.py @@ -0,0 +1,48 @@ +""" +Shell integration helpers for Cortex. + +This module is used by Bash/Zsh hotkey bindings to convert +natural language input into a suggested shell command. +""" + +from typing import Optional + + +def suggest_command(user_input: str) -> Optional[str]: + """ + Generate a shell command suggestion from free-form user input. + + Args: + user_input (str): Text currently typed in the shell. + + Returns:a + Optional[str]: Suggested shell command or None if no suggestion. + """ + user_input = user_input.strip() + if not user_input: + return None + + # Import here to keep shell integration lightweight + try: + from cortex.interpreter import interpret + except Exception: + return None + + try: + result = interpret(user_input) + except Exception: + return None + + if not result: + return None + + # Expected result structure: + # { + # "command": "sudo apt install docker", + # ... + # } + command = result.get("command") + if not isinstance(command, str): + return None + + return command.strip() diff --git a/scripts/cortex_bash.sh b/scripts/cortex_bash.sh new file mode 100644 index 00000000..e8c7df08 --- /dev/null +++ b/scripts/cortex_bash.sh @@ -0,0 +1,20 @@ +# Cortex Bash shell integration +# Binds Ctrl+L to send current input to Cortex and replace it with a suggestion + +_cortex_suggest() { + # READLINE_LINE contains the current command line + local input="$READLINE_LINE" + + # Call cortex shell suggestion helper + local suggestion + suggestion="$(cortex _shell_suggest "$input" 2>/dev/null)" + + # If we got a suggestion, replace the current line + if [[ -n "$suggestion" ]]; then + READLINE_LINE="$suggestion" + READLINE_POINT=${#READLINE_LINE} + fi +} + +# Bind Ctrl+L to cortex suggestion +bind -x '"\C-l": _cortex_suggest' diff --git a/scripts/cortex_zsh.zsh b/scripts/cortex_zsh.zsh new file mode 100644 index 00000000..175cf8b8 --- /dev/null +++ b/scripts/cortex_zsh.zsh @@ -0,0 +1,17 @@ +# Cortex Zsh shell integration +# Binds Ctrl+L to send current input to Cortex and replace it with a suggestion + +_cortex_suggest() { + local input="$BUFFER" + local suggestion + + suggestion="$(cortex _shell_suggest "$input" 2>/dev/null)" + + if [[ -n "$suggestion" ]]; then + BUFFER="$suggestion" + CURSOR=${#BUFFER} + fi +} + +zle -N _cortex_suggest +bindkey '^L' _cortex_suggest \ No newline at end of file diff --git a/tests/test_shell_integration.py b/tests/test_shell_integration.py new file mode 100644 index 00000000..b512ecc5 --- /dev/null +++ b/tests/test_shell_integration.py @@ -0,0 +1,11 @@ +from cortex.shell_integration import suggest_command + + +def test_suggest_command_empty(): + assert suggest_command("") is None + + +def test_suggest_command_text(): + # We only check that it does not crash + result = suggest_command("install docker") + assert result is None or isinstance(result, str) \ No newline at end of file