-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathrunApp.py
More file actions
198 lines (158 loc) · 4.79 KB
/
Copy pathrunApp.py
File metadata and controls
198 lines (158 loc) · 4.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#!/usr/bin/env python3
"""
Helm Application Runner
Installs dependencies, starts Helm
Usage:
python runApp.py
"""
import subprocess
import sys
import os
import time
import atexit
import signal
from pathlib import Path
def print_header(title):
"""Print a formatted header"""
print("=" * 40)
print(title)
print("=" * 40)
print()
def print_step(step, title):
"""Print a formatted step header"""
print("-" * 40)
print(f"Step {step}: {title}")
print("-" * 40)
def run_command(command, cwd=None):
"""Run a command and return the exit code"""
try:
result = subprocess.run(
command,
cwd=cwd,
shell=True,
capture_output=False,
text=True
)
return result.returncode
except Exception as e:
print(f"[ERROR] Failed to execute command: {e}")
return 1
def _command_wrapper_name(name: str) -> str:
"""Return the platform-specific npm bin wrapper name."""
if os.name == "nt":
return f"{name}.cmd"
return name
def dependencies_ready() -> bool:
"""Check that the local toolchain needed by the runner is actually present."""
required_paths = [
Path("package.json"),
Path("node_modules"),
Path("node_modules") / ".bin" / _command_wrapper_name("esbuild"),
Path("node_modules") / ".bin" / _command_wrapper_name("vite"),
Path("node_modules") / ".bin" / _command_wrapper_name("electron"),
]
return all(path.exists() for path in required_paths)
# ---- Process management and cleanup ----
_procs = {}
def _register_proc(key, proc):
if proc is not None:
_procs[key] = proc
def _terminate_proc(proc, name):
if not proc:
return
if proc.poll() is not None:
return
pid = proc.pid
try:
proc.terminate()
try:
proc.wait(timeout=5)
except Exception:
proc.kill()
print(f"[CLEANUP] Terminated {name} (PID {pid})")
except Exception as e:
print(f"[WARN] Failed to terminate {name} (PID {pid}): {e}")
def _cleanup():
app = _procs.get('app')
if app is None:
return
print("\n[INFO] Shutting down child processes...")
_terminate_proc(app, 'Helm')
atexit.register(_cleanup)
def _install_signal_handlers():
def handler(signum, frame):
print(f"\n[INFO] Caught signal {signum}. Cleaning up...")
_cleanup()
try:
sys.exit(0)
except SystemExit:
os._exit(0)
for sig in ('SIGINT', 'SIGTERM', 'SIGHUP'):
if hasattr(signal, sig):
try:
signal.signal(getattr(signal, sig), handler)
except Exception:
pass
def main():
start_time = time.time()
print_header("Helm Application Runner")
print(f"[INFO] Startup initiated at {time.strftime('%H:%M:%S')}")
print()
_install_signal_handlers()
# Step 1: Install dependencies
print_step(1, "Installing Dependencies")
if not dependencies_ready():
print("[INFO] Installing npm dependencies...")
if run_command("npm install") != 0:
print("[ERROR] Failed to install dependencies")
sys.exit(1)
print("[SUCCESS] Dependencies installed")
else:
print("[INFO] Dependencies already installed (skipping)")
print()
# Step 2: Build TypeScript
print_step(2, "Building TypeScript")
print("[INFO] Running esbuild (electron + preload + renderer)...")
if run_command("npm run build") != 0:
print("[ERROR] Build failed")
sys.exit(1)
print("[SUCCESS] Build complete")
print()
# Step 3: Start the application
print_step(3, "Starting Helm")
print("[INFO] Starting gamepad CLI controller...")
print("[INFO] Connect your Xbox controller now!")
print()
try:
app_process = subprocess.Popen(
"npx electron .",
shell=True,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0
)
_register_proc('app', app_process)
except Exception as e:
print(f"[ERROR] Failed to start app: {e}")
sys.exit(1)
print()
print("=" * 40)
print("Helm is now running!")
print("=" * 40)
print()
print("[INFO] Controls:")
print(" - D-Pad Up/Down: Switch between CLI sessions")
print(" - Left Trigger: Spawn new Claude Code instance")
print(" - Right Bumper: Spawn new Copilot CLI instance")
print(" - A: Clear screen")
print(" - B: Voice input (long-press spacebar)")
print(" - X/Y: Custom commands per CLI type")
print()
print("[INFO] Press Ctrl+C to stop")
print()
try:
app_process.wait()
except KeyboardInterrupt:
pass
finally:
_cleanup()
if __name__ == "__main__":
main()