1+ import sys
2+ import os
3+ import signal
4+ import logging
5+ import threading
6+ import webbrowser
7+ import subprocess
8+ import shlex
9+ import time
10+ from pathlib import Path
11+ from flask import Flask , request , jsonify , make_response
12+
13+ # --- Imports & Configuration ---
14+ try :
15+ from flask_cors import CORS
16+ HAS_WEB_DEPS = True
17+ except ImportError :
18+ HAS_WEB_DEPS = False
19+
20+ try :
21+ from omnipkg .utils .flask_port_finder import find_free_port
22+ except ImportError :
23+ def find_free_port (start_port = 5000 , ** kwargs ): return start_port
24+
25+ ALLOWED_ORIGIN = "https://omnipkg.1minds3t.workers.dev"
26+ PID_FILE = Path .home () / ".omnipkg" / "web_bridge.pid"
27+
28+ # --- FLASK APP LOGIC (Same as before) ---
29+
30+ def build_cors_preflight_response ():
31+ response = make_response ()
32+ response .headers .add ("Access-Control-Allow-Origin" , ALLOWED_ORIGIN )
33+ response .headers .add ("Access-Control-Allow-Headers" , "Content-Type, Private-Network-Access-Request" )
34+ response .headers .add ("Access-Control-Allow-Methods" , "GET, POST, OPTIONS" )
35+ response .headers .add ("Access-Control-Allow-Private-Network" , "true" )
36+ return response
37+
38+ def corsify_actual_response (response ):
39+ response .headers .add ("Access-Control-Allow-Origin" , ALLOWED_ORIGIN )
40+ response .headers .add ("Access-Control-Allow-Private-Network" , "true" )
41+ return response
42+
43+ def execute_omnipkg_command (cmd_str ):
44+ try :
45+ args = shlex .split (cmd_str )
46+ # Security: Allow only specific commands if needed, or leave open for dev
47+ full_command = [sys .executable , "-m" , "omnipkg" ] + args
48+
49+ # Windows: specific flags to prevent popping up new CMD windows
50+ startupinfo = None
51+ if os .name == 'nt' :
52+ startupinfo = subprocess .STARTUPINFO ()
53+ startupinfo .dwFlags |= subprocess .STARTF_USESHOWWINDOW
54+
55+ result = subprocess .run (
56+ full_command ,
57+ capture_output = True ,
58+ text = True ,
59+ timeout = 60 ,
60+ env = os .environ .copy (),
61+ startupinfo = startupinfo
62+ )
63+ if result .returncode == 0 :
64+ return result .stdout
65+ return f"Error ({ result .returncode } ):\n { result .stderr } \n { result .stdout } "
66+ except Exception as e :
67+ return f"System Error: { str (e )} "
68+
69+ def create_app (port ):
70+ app = Flask (__name__ )
71+ log = logging .getLogger ('werkzeug' )
72+ log .setLevel (logging .ERROR )
73+
74+ @app .route ('/health' , methods = ['GET' , 'OPTIONS' ])
75+ def health ():
76+ if request .method == "OPTIONS" : return build_cors_preflight_response ()
77+ return corsify_actual_response (jsonify ({"status" : "connected" , "port" : port , "version" : "2.0.8" }))
78+
79+ @app .route ('/run' , methods = ['POST' , 'OPTIONS' ])
80+ def run_command ():
81+ if request .method == "OPTIONS" : return build_cors_preflight_response ()
82+ data = request .json
83+ cmd = data .get ('command' , '' )
84+ print (f"⚡ Web Request: omnipkg { cmd } " )
85+ output = execute_omnipkg_command (cmd )
86+ return corsify_actual_response (jsonify ({"output" : output }))
87+ return app
88+
89+ # --- DAEMON MANAGEMENT LOGIC ---
90+
91+ def save_pid (pid ):
92+ PID_FILE .parent .mkdir (parents = True , exist_ok = True )
93+ PID_FILE .write_text (str (pid ))
94+
95+ def get_pid ():
96+ if PID_FILE .exists ():
97+ try :
98+ return int (PID_FILE .read_text ().strip ())
99+ except :
100+ return None
101+ return None
102+
103+ def start_daemon ():
104+ """Starts this script as a detached background process."""
105+ if get_pid ():
106+ print ("⚠️ Web Bridge is already running." )
107+ return
108+
109+ # Path to this script
110+ script_path = os .path .abspath (__file__ )
111+
112+ # Platform specific flags for detaching
113+ if os .name == 'nt' :
114+ # Windows detached process
115+ creationflags = subprocess .DETACHED_PROCESS | subprocess .CREATE_NEW_PROCESS_GROUP
116+ process = subprocess .Popen (
117+ [sys .executable , script_path , "--server-mode" ],
118+ creationflags = creationflags ,
119+ close_fds = True
120+ )
121+ else :
122+ # Linux/Mac detached process (nohup style)
123+ process = subprocess .Popen (
124+ [sys .executable , script_path , "--server-mode" ],
125+ stdout = subprocess .DEVNULL ,
126+ stderr = subprocess .DEVNULL ,
127+ start_new_session = True ,
128+ close_fds = True
129+ )
130+
131+ save_pid (process .pid )
132+ print (f"🚀 OmniPkg Web Bridge started in background (PID: { process .pid } )" )
133+
134+ # We can't know the port immediately in daemon mode easily without a complex handshake,
135+ # so we assume it finds one shortly.
136+ print (f"🌍 Dashboard: { ALLOWED_ORIGIN } " )
137+ webbrowser .open (ALLOWED_ORIGIN )
138+
139+ def stop_daemon ():
140+ pid = get_pid ()
141+ if not pid :
142+ print ("❌ No active Web Bridge found." )
143+ return
144+
145+ try :
146+ os .kill (pid , signal .SIGTERM )
147+ print (f"✅ Stopped Web Bridge (PID: { pid } )" )
148+ except ProcessLookupError :
149+ print ("⚠️ Process not found (already stopped?)" )
150+ except Exception as e :
151+ print (f"❌ Error stopping process: { e } " )
152+
153+ if PID_FILE .exists ():
154+ PID_FILE .unlink ()
155+
156+ def run_server_blocking ():
157+ """The actual server logic (what runs inside the daemon)."""
158+ if not HAS_WEB_DEPS :
159+ return # Can't log, we are detached
160+
161+ try :
162+ port = find_free_port (start_port = 5000 , max_attempts = 50 , reserve = True )
163+ app = create_app (port )
164+
165+ # Optional: Write port to file if you want the CLI to read it later
166+
167+ app .run (port = port , threaded = True )
168+ except Exception as e :
169+ pass # Logging to file recommended here for debugging daemons
170+
171+ if __name__ == "__main__" :
172+ if "--server-mode" in sys .argv :
173+ # This is the background process
174+ run_server_blocking ()
175+ elif "--stop" in sys .argv :
176+ stop_daemon ()
177+ elif "--daemon" in sys .argv :
178+ start_daemon ()
179+ else :
180+ # Foreground mode (for debugging)
181+ print ("Running in foreground..." )
182+ run_server_blocking ()
0 commit comments