|
1 | 1 | """Command handlers for slash commands and bash execution.""" |
2 | 2 |
|
3 | | -import os |
4 | | -import signal |
5 | 3 | import subprocess |
6 | 4 | from pathlib import Path |
7 | 5 |
|
@@ -59,92 +57,33 @@ def execute_bash_command(command: str) -> bool: |
59 | 57 | if not cmd: |
60 | 58 | return True |
61 | 59 |
|
62 | | - process = None |
63 | | - interrupted = False |
64 | | - original_handler = None |
65 | | - |
66 | | - def sigint_handler(signum, frame): |
67 | | - """Custom SIGINT handler - kills subprocess immediately and sets flag.""" |
68 | | - nonlocal interrupted, process |
69 | | - interrupted = True |
70 | | - |
71 | | - # Kill subprocess immediately when Ctrl+C is pressed |
72 | | - if process and process.poll() is None: |
73 | | - try: |
74 | | - if hasattr(os, "killpg"): |
75 | | - os.killpg(os.getpgid(process.pid), signal.SIGINT) |
76 | | - else: |
77 | | - process.send_signal(signal.SIGINT) |
78 | | - except (ProcessLookupError, OSError): |
79 | | - pass |
80 | | - |
81 | 60 | try: |
82 | | - # Install our signal handler (temporarily overrides asyncio's handler) |
83 | | - original_handler = signal.signal(signal.SIGINT, sigint_handler) |
84 | | - |
85 | 61 | console.print() |
86 | 62 | console.print(f"[dim]$ {cmd}[/dim]") |
87 | 63 |
|
88 | | - # Create subprocess in new session (process group) to isolate from parent's signals |
89 | | - process = subprocess.Popen( |
90 | | - cmd, |
91 | | - shell=True, |
92 | | - text=True, |
93 | | - stdin=subprocess.DEVNULL, # Close stdin to prevent interactive commands from hanging |
94 | | - stdout=subprocess.PIPE, |
95 | | - stderr=subprocess.PIPE, |
96 | | - start_new_session=True, # Isolate subprocess in its own process group |
97 | | - cwd=Path.cwd(), |
| 64 | + # Execute the command |
| 65 | + result = subprocess.run( |
| 66 | + cmd, check=False, shell=True, capture_output=True, text=True, timeout=30, cwd=Path.cwd() |
98 | 67 | ) |
99 | 68 |
|
100 | | - try: |
101 | | - # Wait for command to complete with timeout |
102 | | - stdout, stderr = process.communicate(timeout=30) |
103 | | - |
104 | | - # Display output |
105 | | - if stdout: |
106 | | - console.print(stdout, style=COLORS["dim"], markup=False) |
107 | | - if stderr: |
108 | | - console.print(stderr, style="red", markup=False) |
109 | | - |
110 | | - # Check if interrupted via our flag |
111 | | - if interrupted: |
112 | | - console.print("\n[yellow]Command interrupted by user[/yellow]\n") |
113 | | - elif process.returncode != 0: |
114 | | - # Exit code 130 = 128 + SIGINT (2) - command was interrupted |
115 | | - # Exit code -2 also indicates interrupt in some shells |
116 | | - if process.returncode == 130 or process.returncode == -2: |
117 | | - console.print("[yellow]Command interrupted[/yellow]") |
118 | | - else: |
119 | | - console.print(f"[dim]Exit code: {process.returncode}[/dim]") |
120 | | - |
121 | | - except subprocess.TimeoutExpired: |
122 | | - # Timeout - kill the process group |
123 | | - if hasattr(os, "killpg"): |
124 | | - try: |
125 | | - os.killpg(os.getpgid(process.pid), signal.SIGKILL) |
126 | | - except (ProcessLookupError, OSError): |
127 | | - pass |
128 | | - else: |
129 | | - process.kill() |
130 | | - |
131 | | - # Clean up zombie process |
132 | | - try: |
133 | | - process.wait(timeout=1) |
134 | | - except subprocess.TimeoutExpired: |
135 | | - pass |
136 | | - |
137 | | - console.print("[red]Command timed out after 30 seconds[/red]") |
| 69 | + # Display output |
| 70 | + if result.stdout: |
| 71 | + console.print(result.stdout, style=COLORS["dim"], markup=False) |
| 72 | + if result.stderr: |
| 73 | + console.print(result.stderr, style="red", markup=False) |
| 74 | + |
| 75 | + # Show return code if non-zero |
| 76 | + if result.returncode != 0: |
| 77 | + console.print(f"[dim]Exit code: {result.returncode}[/dim]") |
138 | 78 |
|
139 | 79 | console.print() |
140 | 80 | return True |
141 | 81 |
|
| 82 | + except subprocess.TimeoutExpired: |
| 83 | + console.print("[red]Command timed out after 30 seconds[/red]") |
| 84 | + console.print() |
| 85 | + return True |
142 | 86 | except Exception as e: |
143 | 87 | console.print(f"[red]Error executing command: {e}[/red]") |
144 | 88 | console.print() |
145 | 89 | return True |
146 | | - |
147 | | - finally: |
148 | | - # CRITICAL: Always restore original signal handler so asyncio can handle Ctrl+C at prompt |
149 | | - if original_handler is not None: |
150 | | - signal.signal(signal.SIGINT, original_handler) |
0 commit comments