computer-use-ootb-internal 0.0.110__py3-none-any.whl → 0.0.112__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,9 @@ import platform # Add platform import
8
8
  import pyautogui # Add pyautogui import
9
9
  import webbrowser # Add webbrowser import
10
10
  import os # Import os for path joining
11
+ import logging # Import logging
12
+ import importlib # For dynamic imports
13
+ import pkgutil # To find modules
11
14
  from fastapi import FastAPI, Request
12
15
  from fastapi.responses import JSONResponse
13
16
  from fastapi.middleware.cors import CORSMiddleware
@@ -90,67 +93,47 @@ class SharedState:
90
93
  shared_state = None
91
94
  rate_limiter = RateLimiter(interval_seconds=2)
92
95
 
93
- # Add the new prepare_environment function here
96
+ # Set up logging for this module
97
+ log = logging.getLogger(__name__)
98
+
94
99
  def prepare_environment(state):
95
- """Prepares the environment before starting the main processing loop, e.g., opening specific apps."""
96
- if platform.system() == "Windows":
97
- # Assuming Star Rail mode is indicated by user_id containing "star_rail"
98
- # You might need to adjust this condition based on the actual logic in run_teachmode_args
99
- is_star_rail = "star_rail" in state.user_id.lower() or \
100
- "star_rail" in state.trace_id.lower() or \
101
- "hero_case" in state.trace_id.lower()
102
-
103
- if is_star_rail:
104
- print("Star Rail mode detected on Windows. Opening Edge browser...")
105
- url = "https://sr.mihoyo.com/cloud/#/"
106
- browser_opened = False
107
- try:
108
- # Use only webbrowser.open
109
- print(f"Attempting to open {url} using webbrowser.open()...")
110
- if webbrowser.open(url):
111
- print(f"Successfully requested browser to open {url} via webbrowser.open().")
112
- browser_opened = True
113
- else:
114
- print("webbrowser.open() returned False, indicating potential failure.")
115
-
116
- if not browser_opened:
117
- print("ERROR: Failed to confirm browser opening via webbrowser.open().")
118
- # Still proceed to click attempt
119
-
120
- # Add pyautogui click after attempting to open the browser
121
- print("Proceeding with pyautogui actions...")
122
- time.sleep(5) # Wait time for the browser to load
123
-
124
- # Print detected screen size
125
- screen_width, screen_height = pyautogui.size()
126
- print(f"Detected screen size: {screen_width}x{screen_height}")
127
-
128
- click_x = int(screen_width * (1036 / 1280))
129
- click_y = int(screen_height * (500 / 720))
130
- print(f"Calculated click coordinates: ({click_x}, {click_y})")
131
-
132
- # Disable failsafe before clicking
133
- pyautogui.FAILSAFE = False
134
- print("PyAutoGUI failsafe temporarily disabled.")
135
-
136
- print(f"Clicking at coordinates: ({click_x}, {click_y})")
137
- pyautogui.click(click_x, click_y)
138
- time.sleep(2)
139
- pyautogui.click(click_x, click_y)
140
-
141
- # Re-enable failsafe (optional, as script might end anyway)
142
- # pyautogui.FAILSAFE = True
143
- # print("PyAutoGUI failsafe re-enabled.")
144
-
145
- except Exception as e:
146
- print(f"Error during environment preparation (browser/click): {e}")
147
- finally:
148
- # Ensure failsafe is re-enabled if an error occurs after disabling it
149
- pyautogui.FAILSAFE = True
150
- print("PyAutoGUI failsafe re-enabled.")
151
- else:
152
- # Placeholder for potential preparations on other OS or non-Star Rail modes
153
- print("Environment preparation: No specific actions required for this OS/mode.")
100
+ """Dynamically loads and runs preparation logic based on software name."""
101
+ # TODO: Replace hardcoded software name with value from shared_state when available
102
+ software_name = "star rail"
103
+ # Normalize the software name to be a valid Python module name
104
+ # Replace spaces/hyphens with underscores, convert to lowercase
105
+ module_name_base = software_name.replace(" ", "_").replace("-", "_").lower()
106
+ module_to_run = f"{module_name_base}_prepare"
107
+
108
+ log.info(f"Attempting preparation for software: '{software_name}' (Module: '{module_to_run}')")
109
+
110
+ try:
111
+ # Construct the full module path within the package
112
+ prep_package = "computer_use_ootb_internal.preparation"
113
+ full_module_path = f"{prep_package}.{module_to_run}"
114
+
115
+ # Dynamically import the module
116
+ # Check if module exists first using pkgutil to avoid import errors
117
+ # Note: pkgutil.find_loader might be deprecated, consider importlib.util.find_spec
118
+ loader = pkgutil.find_loader(full_module_path)
119
+ if loader is None:
120
+ log.warning(f"Preparation module '{full_module_path}' not found. Skipping preparation.")
121
+ return
122
+
123
+ prep_module = importlib.import_module(full_module_path)
124
+
125
+ # Check if the module has the expected function
126
+ if hasattr(prep_module, "run_preparation") and callable(prep_module.run_preparation):
127
+ log.info(f"Running preparation function from {full_module_path}...")
128
+ prep_module.run_preparation(state)
129
+ log.info(f"Preparation function from {full_module_path} completed.")
130
+ else:
131
+ log.warning(f"Module {full_module_path} found, but does not have a callable 'run_preparation' function. Skipping.")
132
+
133
+ except ModuleNotFoundError:
134
+ log.warning(f"Preparation module '{full_module_path}' not found. Skipping preparation.")
135
+ except Exception as e:
136
+ log.error(f"Error during dynamic preparation loading/execution for '{module_to_run}': {e}", exc_info=True)
154
137
 
155
138
 
156
139
  @app.post("/update_params")
@@ -177,7 +160,7 @@ async def update_parameters(request: Request):
177
160
 
178
161
  log_ootb_request(shared_state.server_url, "update_params", data)
179
162
 
180
- # Call the preparation function here, after parameters are updated
163
+ # Call the (now dynamic) preparation function here, after parameters are updated
181
164
  prepare_environment(shared_state)
182
165
 
183
166
  return JSONResponse(
@@ -0,0 +1,436 @@
1
+ # src/computer_use_ootb_internal/guard_service.py
2
+ import sys
3
+ import os
4
+ import time
5
+ import logging
6
+ import subprocess
7
+ import pathlib
8
+ import ctypes
9
+ import requests # For server polling
10
+ import servicemanager # From pywin32
11
+ import win32serviceutil # From pywin32
12
+ import win32service # From pywin32
13
+ import win32event # From pywin32
14
+ import win32api # From pywin32
15
+ import win32process # From pywin32
16
+ import win32security # From pywin32
17
+ import win32profile # From pywin32
18
+ import win32ts # From pywin32 (Terminal Services API)
19
+ import win32con # Added import
20
+ import psutil # For process/user info
21
+
22
+ # --- Configuration ---
23
+ # Internal service name
24
+ _SERVICE_NAME = "OOTBGuardService"
25
+ # Display name in Windows Services MMC
26
+ _SERVICE_DISPLAY_NAME = "OOTB Guard Service"
27
+ # Description in Windows Services MMC
28
+ _SERVICE_DESCRIPTION = "Background service for OOTB monitoring and remote management."
29
+ # Package name for updates
30
+ _PACKAGE_NAME = "computer-use-ootb-internal"
31
+ # Main module to start/stop for users
32
+ _OOTB_MODULE = "computer_use_ootb_internal.app_teachmode"
33
+ # Server endpoint to poll for commands (replace with your actual URL)
34
+ _SERVER_COMMAND_URL = "http://52.160.105.102:7000/api/guard" # Using HTTP as port was specified
35
+ # How often to poll the server (in seconds)
36
+ _POLLING_INTERVAL = 60
37
+ # Placeholder for a machine identifier or API key for the server
38
+ _MACHINE_ID = "YOUR_MACHINE_ID_OR_API_KEY" # EXAMPLE - Implement actual ID/auth
39
+ # Log file location (consider using Windows Event Log instead for production)
40
+ _LOG_FILE = pathlib.Path(os.environ['PROGRAMDATA']) / "OOTBGuardService" / "guard.log"
41
+ # --- End Configuration ---
42
+
43
+ # Ensure log directory exists
44
+ _LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
45
+
46
+ logging.basicConfig(
47
+ filename=_LOG_FILE,
48
+ level=logging.INFO,
49
+ format='%(asctime)s %(levelname)s %(message)s'
50
+ )
51
+
52
+ def get_python_executable():
53
+ """Gets the quoted path to the current python executable."""
54
+ python_exe = sys.executable
55
+ if " " in python_exe and not python_exe.startswith('"'):
56
+ python_exe = f'"{python_exe}"'
57
+ return python_exe
58
+
59
+ def get_pip_executable():
60
+ """Tries to locate the pip executable in the same environment."""
61
+ python_path = pathlib.Path(sys.executable)
62
+ pip_path = python_path.parent / "Scripts" / "pip.exe"
63
+ if pip_path.exists():
64
+ pip_exe = str(pip_path)
65
+ if " " in pip_exe and not pip_exe.startswith('"'):
66
+ pip_exe = f'"{pip_exe}"'
67
+ return pip_exe
68
+ else:
69
+ logging.warning("pip.exe not found in Scripts directory. Falling back to 'python -m pip'.")
70
+ return f"{get_python_executable()} -m pip"
71
+
72
+ def log_info(msg):
73
+ logging.info(msg)
74
+ try:
75
+ servicemanager.LogInfoMsg(str(msg)) # Also log to Windows Event Log Application channel
76
+ except Exception as e:
77
+ logging.warning(f"Could not write to Windows Event Log: {e}") # Avoid crashing service if event log fails
78
+
79
+ def log_error(msg, exc_info=False):
80
+ logging.error(msg, exc_info=exc_info)
81
+ try:
82
+ servicemanager.LogErrorMsg(str(msg))
83
+ except Exception as e:
84
+ logging.warning(f"Could not write error to Windows Event Log: {e}")
85
+
86
+ class GuardService(win32serviceutil.ServiceFramework):
87
+ _svc_name_ = _SERVICE_NAME
88
+ _svc_display_name_ = _SERVICE_DISPLAY_NAME
89
+ _svc_description_ = _SERVICE_DESCRIPTION
90
+
91
+ def __init__(self, args):
92
+ win32serviceutil.ServiceFramework.__init__(self, args)
93
+ self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
94
+ self.is_running = True
95
+ self.python_exe = get_python_executable()
96
+ self.pip_command_base = get_pip_executable()
97
+ self.ootb_command = f"{self.python_exe} -m {_OOTB_MODULE}"
98
+ self.session = requests.Session() # Reuse session for polling
99
+
100
+ def SvcStop(self):
101
+ self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
102
+ win32event.SetEvent(self.hWaitStop)
103
+ self.is_running = False
104
+ log_info(f"{_SERVICE_NAME} is stopping.")
105
+
106
+ def SvcDoRun(self):
107
+ servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
108
+ servicemanager.PYS_SERVICE_STARTED,
109
+ (self._svc_name_, ''))
110
+ self.main_loop()
111
+ # If main_loop exits cleanly (shouldn't happen with while self.is_running)
112
+ log_info(f"{_SERVICE_NAME} main loop exited.")
113
+
114
+
115
+ def main_loop(self):
116
+ log_info(f"{_SERVICE_NAME} started. Polling {_SERVER_COMMAND_URL} every {_POLLING_INTERVAL}s.")
117
+ while self.is_running:
118
+ try:
119
+ self.poll_server_for_commands()
120
+ except Exception as e:
121
+ log_error(f"Error in main loop polling cycle: {e}", exc_info=True)
122
+
123
+ # Wait for stop event or timeout
124
+ rc = win32event.WaitForSingleObject(self.hWaitStop, _POLLING_INTERVAL * 1000)
125
+ if rc == win32event.WAIT_OBJECT_0:
126
+ # Stop event signaled
127
+ break
128
+ log_info(f"{_SERVICE_NAME} is shutting down main loop.")
129
+
130
+
131
+ def poll_server_for_commands(self):
132
+ log_info(f"Polling server for commands...")
133
+ try:
134
+ headers = {'Authorization': f'Bearer {_MACHINE_ID}'} # Example auth
135
+ # Add machine identifier if needed by server
136
+ params = {'machine_id': os.getenv('COMPUTERNAME', 'unknown')}
137
+ response = self.session.get(_SERVER_COMMAND_URL, headers=headers, params=params, timeout=30)
138
+ response.raise_for_status() # Raise exception for bad status codes
139
+
140
+ commands = response.json() # Expecting a list of command objects
141
+ if not commands:
142
+ # log_info("No commands received.") # Reduce log noise
143
+ return
144
+
145
+ log_info(f"Received {len(commands)} command(s). Processing...")
146
+ for command in commands:
147
+ action = command.get("action")
148
+ target = command.get("target_user", "all_active") # Default to all
149
+ command_id = command.get("command_id", "N/A") # Optional: for reporting status
150
+
151
+ log_info(f"Processing Command ID {command_id}: action='{action}', target='{target}'")
152
+ status = "failed" # Default status
153
+ try:
154
+ if action == "update":
155
+ status = self.handle_update()
156
+ elif action == "stop_ootb":
157
+ status = self.handle_stop(target)
158
+ elif action == "start_ootb":
159
+ status = self.handle_start(target)
160
+ else:
161
+ log_error(f"Unknown action received: {action}")
162
+ status = "unknown_action"
163
+ except Exception as handler_ex:
164
+ log_error(f"Error executing action '{action}' for command {command_id}: {handler_ex}", exc_info=True)
165
+ status = "execution_error"
166
+
167
+ # TODO: Add mechanism to report command completion/failure back to server
168
+ # Example: self.report_command_status(command_id, status)
169
+ log_info(f"Finished processing Command ID {command_id}: Status='{status}'")
170
+
171
+ except requests.exceptions.RequestException as e:
172
+ log_error(f"Failed to poll server: {e}")
173
+ except Exception as e:
174
+ log_error(f"Error processing server commands: {e}", exc_info=True)
175
+
176
+
177
+ def handle_update(self):
178
+ log_info("Executing OOTB update...")
179
+ if not self.pip_command_base:
180
+ log_error("Cannot update: pip command not found.")
181
+ return "failed_pip_not_found"
182
+
183
+ update_command = f"{self.pip_command_base} install --upgrade --no-cache-dir {_PACKAGE_NAME}"
184
+ log_info(f"Running update command: {update_command}")
185
+ try:
186
+ # Run update command
187
+ result = subprocess.run(update_command, shell=True, capture_output=True, text=True, check=True, timeout=300)
188
+ log_info(f"Update successful: \nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}")
189
+ return "success"
190
+ except subprocess.CalledProcessError as e:
191
+ log_error(f"Update failed (Exit Code {e.returncode}):\nSTDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}")
192
+ return f"failed_exit_{e.returncode}"
193
+ except subprocess.TimeoutExpired:
194
+ log_error(f"Update command timed out.")
195
+ return "failed_timeout"
196
+ except Exception as e:
197
+ log_error(f"Unexpected error during update: {e}", exc_info=True)
198
+ return "failed_exception"
199
+
200
+
201
+ def _get_ootb_processes(self, target_user="all_active"):
202
+ """Finds OOTB processes, optionally filtering by username."""
203
+ ootb_procs = []
204
+ target_pid_list = []
205
+ try:
206
+ # Get a list of usernames we are interested in
207
+ target_users = set()
208
+ if target_user == "all_active":
209
+ for user_session in psutil.users():
210
+ # Normalize username (remove domain if present)
211
+ username = user_session.name.split('\\')[-1]
212
+ target_users.add(username.lower())
213
+ else:
214
+ target_users.add(target_user.lower())
215
+
216
+ log_info(f"Searching for OOTB processes for users: {target_users}")
217
+
218
+ for proc in psutil.process_iter(['pid', 'name', 'username', 'cmdline']):
219
+ try:
220
+ pinfo = proc.info
221
+ # Normalize process username
222
+ proc_username = pinfo['username']
223
+ if proc_username:
224
+ proc_username = proc_username.split('\\')[-1].lower()
225
+
226
+ # Check if process user is one of the targets
227
+ if proc_username in target_users:
228
+ # Check if command line matches our OOTB app pattern
229
+ cmdline = ' '.join(pinfo['cmdline']) if pinfo['cmdline'] else ''
230
+ # Simple check: does it contain python executable and the module name?
231
+ if (self.python_exe.strip('"') in cmdline) and (_OOTB_MODULE in cmdline):
232
+ log_info(f"Found matching OOTB process: PID={pinfo['pid']}, User={pinfo['username']}, Cmd={cmdline}")
233
+ ootb_procs.append(proc)
234
+ target_pid_list.append(pinfo['pid'])
235
+
236
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
237
+ continue # Process might have died or we lack permissions
238
+ log_info(f"Found {len(ootb_procs)} OOTB process(es) matching criteria: {target_pid_list}")
239
+ except Exception as e:
240
+ log_error(f"Error enumerating processes: {e}", exc_info=True)
241
+ return ootb_procs
242
+
243
+
244
+ def handle_stop(self, target_user="all_active"):
245
+ log_info(f"Executing stop OOTB for target '{target_user}'...")
246
+ stopped_count = 0
247
+ procs_to_stop = self._get_ootb_processes(target_user)
248
+
249
+ if not procs_to_stop:
250
+ log_info("No running OOTB processes found for target.")
251
+ return "no_process_found"
252
+
253
+ for proc in procs_to_stop:
254
+ try:
255
+ username = proc.info.get('username', 'unknown_user')
256
+ log_info(f"Terminating process PID={proc.pid}, User={username}")
257
+ proc.terminate() # Ask nicely first
258
+ try:
259
+ proc.wait(timeout=3) # Wait a bit
260
+ log_info(f"Process PID={proc.pid} terminated successfully.")
261
+ stopped_count += 1
262
+ except psutil.TimeoutExpired:
263
+ log_warning(f"Process PID={proc.pid} did not terminate gracefully, killing.")
264
+ proc.kill()
265
+ stopped_count += 1
266
+ except psutil.NoSuchProcess:
267
+ log_info(f"Process PID={proc.pid} already terminated.")
268
+ stopped_count +=1 # Count it if it disappeared
269
+ except psutil.AccessDenied:
270
+ log_error(f"Access denied trying to terminate process PID={proc.pid}. Service might lack privileges?")
271
+ except Exception as e:
272
+ log_error(f"Error stopping process PID={proc.pid}: {e}", exc_info=True)
273
+
274
+ log_info(f"Finished stopping OOTB. Terminated {stopped_count} process(es).")
275
+ return f"success_stopped_{stopped_count}"
276
+
277
+
278
+ def handle_start(self, target_user="all_active"):
279
+ log_info(f"Executing start OOTB for target '{target_user}'...")
280
+ started_count = 0
281
+ target_users_started = set()
282
+ users_failed_to_start = set()
283
+
284
+ try:
285
+ sessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE)
286
+ active_sessions = {} # Store user: session_id for active sessions
287
+
288
+ for session in sessions:
289
+ # Look for Active sessions, potentially disconnected ones too?
290
+ # For now, only WTSActive
291
+ if session['State'] == win32ts.WTSActive:
292
+ try:
293
+ user = win32ts.WTSQuerySessionInformation(win32ts.WTS_CURRENT_SERVER_HANDLE, session['SessionId'], win32ts.WTSUserName)
294
+ if user: # Filter out system sessions etc.
295
+ normalized_user = user.lower()
296
+ active_sessions[normalized_user] = session['SessionId']
297
+ except Exception as query_err:
298
+ log_warning(f"Could not query session {session['SessionId']}: {query_err}")
299
+
300
+ log_info(f"Found active user sessions: {active_sessions}")
301
+
302
+ target_session_map = {} # user:session_id
303
+ if target_user == "all_active":
304
+ target_session_map = active_sessions
305
+ else:
306
+ normalized_target = target_user.lower()
307
+ if normalized_target in active_sessions:
308
+ target_session_map[normalized_target] = active_sessions[normalized_target]
309
+ else:
310
+ log_warning(f"Target user '{target_user}' not found in active sessions.")
311
+ return "failed_user_not_active"
312
+
313
+ if not target_session_map:
314
+ log_info("No target user sessions found to start OOTB in.")
315
+ return "failed_no_target_sessions"
316
+
317
+ # Check if OOTB is already running for the target users
318
+ running_procs = self._get_ootb_processes(target_user)
319
+ users_already_running = set()
320
+ for proc in running_procs:
321
+ try:
322
+ proc_username = proc.info.get('username')
323
+ if proc_username:
324
+ users_already_running.add(proc_username.split('\\')[-1].lower())
325
+ except Exception:
326
+ pass # Ignore errors getting username here
327
+
328
+ log_info(f"Users already running OOTB: {users_already_running}")
329
+
330
+ for user, session_id in target_session_map.items():
331
+ token = None # Ensure token is reset/defined
332
+ try:
333
+ if user in users_already_running:
334
+ log_info(f"OOTB already seems to be running for user '{user}'. Skipping start.")
335
+ continue
336
+
337
+ log_info(f"Attempting to start OOTB for user '{user}' in session {session_id}...")
338
+
339
+ # Get user token
340
+ token = win32ts.WTSQueryUserToken(session_id)
341
+
342
+ # Create environment block for the user
343
+ env = win32profile.CreateEnvironmentBlock(token, False)
344
+
345
+ # Create startup info
346
+ startup = win32process.STARTUPINFO()
347
+ startup.dwFlags = win32process.STARTF_USESHOWWINDOW
348
+ # Attempt to show window on user's desktop
349
+ # Requires Service to have "Allow service to interact with desktop" checked
350
+ # AND Interactive Services Detection service running (often disabled now).
351
+ # May be better to run hidden (SW_HIDE) or default.
352
+ startup.wShowWindow = win32con.SW_SHOW
353
+ startup.lpDesktop = 'winsta0\\default' # Try targeting default interactive desktop
354
+
355
+ # Create process as user
356
+ # Needs SeAssignPrimaryTokenPrivilege, SeIncreaseQuotaPrivilege for service account.
357
+ creation_flags = win32process.CREATE_NEW_CONSOLE | win32process.CREATE_UNICODE_ENVIRONMENT
358
+
359
+ hProcess, hThread, dwPid, dwTid = win32process.CreateProcessAsUser(
360
+ token, # User token
361
+ self.python_exe, # Application name (python executable)
362
+ self.ootb_command, # Command line
363
+ None, # Process attributes
364
+ None, # Thread attributes
365
+ False, # Inherit handles
366
+ creation_flags, # Creation flags
367
+ env, # Environment
368
+ None, # Current directory (use default)
369
+ startup # Startup info
370
+ )
371
+ log_info(f"Successfully started OOTB for user '{user}' (PID: {dwPid}).")
372
+ started_count += 1
373
+ target_users_started.add(user)
374
+ # Close handles immediately
375
+ win32api.CloseHandle(hProcess)
376
+ win32api.CloseHandle(hThread)
377
+
378
+ except Exception as proc_err:
379
+ log_error(f"Failed to start OOTB for user '{user}' in session {session_id}: {proc_err}", exc_info=True)
380
+ users_failed_to_start.add(user)
381
+ finally:
382
+ # Ensure token handle is always closed if obtained
383
+ if token:
384
+ try: win32api.CloseHandle(token)
385
+ except: pass
386
+
387
+
388
+ log_info(f"Finished starting OOTB. Started {started_count} new instance(s). Failed for users: {users_failed_to_start or 'None'}")
389
+ if users_failed_to_start:
390
+ return f"partial_success_started_{started_count}_failed_for_{len(users_failed_to_start)}"
391
+ elif started_count > 0:
392
+ return f"success_started_{started_count}"
393
+ else:
394
+ return "no_action_needed_already_running"
395
+
396
+ except Exception as e:
397
+ log_error(f"Error during start OOTB process: {e}", exc_info=True)
398
+ return "failed_exception"
399
+
400
+
401
+ # This block is essential for the service framework to handle
402
+ # command-line arguments like 'install', 'start', 'stop', 'remove', 'debug'.
403
+ if __name__ == '__main__':
404
+ # Add logic to allow debugging from command line easily
405
+ if len(sys.argv) > 1 and sys.argv[1] == 'debug':
406
+ log_info("Starting service in debug mode...")
407
+ service_instance = GuardService(sys.argv)
408
+ service_instance.is_running = True # Ensure loop runs
409
+ try:
410
+ # Run the main loop directly for debugging
411
+ service_instance.main_loop()
412
+ # Simulate stop signal for clean exit in debug
413
+ # service_instance.SvcStop() # Or let it run until Ctrl+C?
414
+ except KeyboardInterrupt:
415
+ log_info("Debug mode interrupted by user.")
416
+ service_instance.SvcStop() # Attempt clean stop
417
+ log_info("Debug mode finished.")
418
+ elif len(sys.argv) == 1:
419
+ # Called without arguments, run as a service instance via SCM
420
+ try:
421
+ servicemanager.Initialize()
422
+ servicemanager.PrepareToHostSingle(GuardService)
423
+ servicemanager.StartServiceCtrlDispatcher()
424
+ except win32service.error as details:
425
+ import winerror
426
+ if details.winerror == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
427
+ print(f"Error: Cannot connect to Service Control Manager.")
428
+ print(f"Use 'python {os.path.basename(__file__)} install|start|stop|remove|debug'")
429
+ else:
430
+ print(f"Error preparing service: {details}")
431
+ except Exception as e:
432
+ print(f"Unexpected error initializing service: {e}")
433
+
434
+ else:
435
+ # Called with install/start/stop/remove args, let ServiceFramework handle them
436
+ win32serviceutil.HandleCommandLine(GuardService)
@@ -0,0 +1 @@
1
+ # This file makes the 'preparation' directory a Python package
@@ -0,0 +1,65 @@
1
+ # src/computer_use_ootb_internal/preparation/star_rail_prepare.py
2
+ import time
3
+ import platform
4
+ import pyautogui
5
+ import webbrowser
6
+ import logging # Use logging instead of print for better practice
7
+
8
+ # Set up logging for this module if needed, or rely on root logger
9
+ log = logging.getLogger(__name__)
10
+
11
+ def run_preparation(state):
12
+ """
13
+ Performs environment preparation specific to Star Rail on Windows.
14
+ Opens the specified URL in Edge and performs initial clicks.
15
+ """
16
+ if platform.system() != "Windows":
17
+ log.info("Star Rail preparation skipped: Not running on Windows.")
18
+ return
19
+
20
+ log.info("Star Rail preparation: Starting environment setup on Windows...")
21
+ url = "https://sr.mihoyo.com/cloud/#/" # Consider making this configurable later
22
+ browser_opened = False
23
+ try:
24
+ # Use only webbrowser.open
25
+ log.info(f"Attempting to open {url} using webbrowser.open()...")
26
+ if webbrowser.open(url):
27
+ log.info(f"Successfully requested browser to open {url} via webbrowser.open().")
28
+ browser_opened = True
29
+ else:
30
+ log.warning("webbrowser.open() returned False, indicating potential failure.")
31
+
32
+ if not browser_opened:
33
+ log.error("Failed to confirm browser opening via webbrowser.open(). Will still attempt clicks.")
34
+
35
+ # Add pyautogui click after attempting to open the browser
36
+ log.info("Proceeding with pyautogui actions...")
37
+ time.sleep(5) # Wait time for the browser to load
38
+
39
+ # Get screen size
40
+ screen_width, screen_height = pyautogui.size()
41
+ log.info(f"Detected screen size: {screen_width}x{screen_height}")
42
+
43
+ # Calculate click coordinates based on a reference resolution (e.g., 1280x720)
44
+ # TODO: Make these coordinates more robust or configurable
45
+ click_x = int(screen_width * (1036 / 1280))
46
+ click_y = int(screen_height * (500 / 720))
47
+ log.info(f"Calculated click coordinates: ({click_x}, {click_y})")
48
+
49
+ # Disable failsafe before clicking
50
+ pyautogui.FAILSAFE = False
51
+ log.info("PyAutoGUI failsafe temporarily disabled.")
52
+
53
+ log.info(f"Clicking at coordinates: ({click_x}, {click_y})")
54
+ pyautogui.click(click_x, click_y)
55
+ time.sleep(2)
56
+ pyautogui.click(click_x, click_y) # Double click?
57
+
58
+ log.info("Star Rail preparation clicks completed.")
59
+
60
+ except Exception as e:
61
+ log.error(f"Error during Star Rail preparation (browser/click): {e}", exc_info=True)
62
+ finally:
63
+ # Ensure failsafe is re-enabled
64
+ pyautogui.FAILSAFE = True
65
+ log.info("PyAutoGUI failsafe re-enabled.")
@@ -0,0 +1,142 @@
1
+ # src/computer_use_ootb_internal/service_manager.py
2
+ import sys
3
+ import os
4
+ import inspect
5
+ import subprocess
6
+ import ctypes
7
+ import platform
8
+ import time
9
+
10
+ # Constants need to match guard_service.py
11
+ _SERVICE_NAME = "OOTBGuardService"
12
+ _SERVICE_DISPLAY_NAME = "OOTB Guard Service"
13
+
14
+ def is_admin():
15
+ """Check if the script is running with administrative privileges."""
16
+ if platform.system() != "Windows":
17
+ return False # Only applicable on Windows
18
+ try:
19
+ return ctypes.windll.shell32.IsUserAnAdmin()
20
+ except:
21
+ return False
22
+
23
+ def get_service_module_path():
24
+ """Gets the absolute path to the guard_service.py module."""
25
+ # Find the path relative to this script's location
26
+ # This assumes service_manager.py and guard_service.py are in the same installed package directory
27
+ try:
28
+ current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
29
+ service_module = os.path.join(current_dir, "guard_service.py")
30
+ if not os.path.exists(service_module):
31
+ raise FileNotFoundError(f"guard_service.py not found adjacent to service_manager.py in {current_dir}")
32
+ return service_module
33
+ except Exception as e:
34
+ # Fallback if inspect fails (e.g., in some frozen environments)
35
+ # Try finding it relative to the script itself? Unreliable.
36
+ # Let's try sys.prefix - might work in standard venv/conda installs
37
+ try:
38
+ # sys.prefix points to the environment root (e.g., C:\path\to\env)
39
+ # Package likely installed in Lib\site-packages\<package_name>
40
+ # This depends heavily on installation layout
41
+ package_name = __name__.split('.')[0] # Should be 'computer_use_ootb_internal'
42
+ site_packages_path = os.path.join(sys.prefix, 'Lib', 'site-packages')
43
+ module_dir = os.path.join(site_packages_path, package_name)
44
+ service_module = os.path.join(module_dir, "guard_service.py")
45
+ if os.path.exists(service_module):
46
+ print(f"Warning: Found service module via sys.prefix fallback: {service_module}")
47
+ return service_module
48
+ else:
49
+ raise FileNotFoundError(f"guard_service.py not found via inspect or sys.prefix fallback (checked {module_dir})")
50
+ except Exception as fallback_e:
51
+ raise FileNotFoundError(f"Could not find guard_service.py using inspect ({e}) or sys.prefix ({fallback_e}). Check installation.")
52
+
53
+
54
+ def run_service_command(command_args, check_errors=True):
55
+ """Runs the guard_service.py script with specified command-line args."""
56
+ if not is_admin():
57
+ print("Error: Administrative privileges are required to manage the service.", file=sys.stderr)
58
+ print("Please run this command from an Administrator Command Prompt or PowerShell.", file=sys.stderr)
59
+ return False
60
+
61
+ try:
62
+ python_exe = sys.executable # Use the same python that's running this script
63
+ service_script = get_service_module_path()
64
+ except FileNotFoundError as e:
65
+ print(f"Error: {e}", file=sys.stderr)
66
+ return False
67
+
68
+ # Quote paths if they contain spaces
69
+ if " " in python_exe and not python_exe.startswith('"'):
70
+ python_exe = f'"{python_exe}"'
71
+ if " " in service_script and not service_script.startswith('"'):
72
+ service_script = f'"{service_script}"'
73
+
74
+ # Construct command using list to avoid shell quoting issues
75
+ cmd = [sys.executable, get_service_module_path()] + command_args
76
+ print(f"Executing command: {' '.join(cmd)}")
77
+
78
+ try:
79
+ # Run the command. Use shell=False with list of args.
80
+ # Capture output to check for specific errors if needed, but print it too.
81
+ result = subprocess.run(cmd, capture_output=True, text=True, check=check_errors, encoding='utf-8')
82
+ if result.stdout:
83
+ print("Command STDOUT:")
84
+ print(result.stdout)
85
+ if result.stderr:
86
+ print("Command STDERR:")
87
+ print(result.stderr)
88
+ print(f"Command {' '.join(command_args)} executed successfully.")
89
+ return True
90
+ except FileNotFoundError as e:
91
+ print(f"Error: Could not find Python executable or service script during execution.", file=sys.stderr)
92
+ print(f" Details: {e}", file=sys.stderr)
93
+ return False
94
+ except subprocess.CalledProcessError as e:
95
+ print(f"Error executing service command {' '.join(command_args)} (Exit Code {e.returncode}).", file=sys.stderr)
96
+ if e.stdout:
97
+ print("Subprocess STDOUT:")
98
+ print(e.stdout)
99
+ if e.stderr:
100
+ print("Subprocess STDERR:")
101
+ print(e.stderr)
102
+ return False
103
+ except Exception as e:
104
+ print(f"An unexpected error occurred running service command: {e}", file=sys.stderr)
105
+ return False
106
+
107
+
108
+ def install_and_start():
109
+ """Installs and starts the Guard Service."""
110
+ print(f"Attempting to install/update service: '{_SERVICE_NAME}' ('{_SERVICE_DISPLAY_NAME}')")
111
+ # Use 'update' which handles install or updates display name/path if needed
112
+ if run_service_command(['--startup', 'auto', 'update']): # Set startup to auto, then update
113
+ print(f"\nAttempting to start service: '{_SERVICE_NAME}' (waiting a few seconds first)")
114
+ time.sleep(3) # Give SCM time to register the update
115
+ if run_service_command(['start']):
116
+ print(f"\nService '{_SERVICE_NAME}' installed/updated and started successfully.")
117
+ else:
118
+ print(f"\nWarning: Service '{_SERVICE_NAME}' installed/updated but failed to start.", file=sys.stderr)
119
+ print(f" Check service logs ('C:\ProgramData\OOTBGuardService\guard.log') or Windows Event Viewer for details.", file=sys.stderr)
120
+ print(f" You may need to start it manually via 'services.msc' or `net start {_SERVICE_NAME}`.")
121
+ else:
122
+ print(f"\nService '{_SERVICE_NAME}' installation/update failed.", file=sys.stderr)
123
+
124
+
125
+ def stop_and_remove():
126
+ """Stops and removes the Guard Service."""
127
+ print(f"Attempting to stop service: '{_SERVICE_NAME}' (will ignore errors if not running)")
128
+ # Run stop first, ignore errors (check_errors=False)
129
+ run_service_command(['stop'], check_errors=False)
130
+ time.sleep(2) # Give service time to stop
131
+
132
+ print(f"\nAttempting to remove service: '{_SERVICE_NAME}'")
133
+ if run_service_command(['remove']):
134
+ print(f"\nService '{_SERVICE_NAME}' stopped (if running) and removed successfully.")
135
+ else:
136
+ print(f"\nService '{_SERVICE_NAME}' removal failed.", file=sys.stderr)
137
+ print(f" Ensure the service was stopped first, or check permissions.", file=sys.stderr)
138
+
139
+ if __name__ == '__main__':
140
+ # Allow calling functions directly for testing if needed
141
+ print("This script provides service management commands.")
142
+ print("Use 'ootb-install-service' or 'ootb-remove-service' as Administrator.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: computer-use-ootb-internal
3
- Version: 0.0.110
3
+ Version: 0.0.112
4
4
  Summary: Computer Use OOTB
5
5
  Author-email: Siyuan Hu <siyuan.hu.sg@gmail.com>
6
6
  Requires-Python: >=3.11
@@ -12,17 +12,17 @@ Requires-Dist: jsonschema==4.22.0
12
12
  Requires-Dist: litellm
13
13
  Requires-Dist: matplotlib
14
14
  Requires-Dist: opencv-python
15
- Requires-Dist: pre-commit==3.8.0
15
+ Requires-Dist: psutil>=5.0; sys_platform == 'win32'
16
16
  Requires-Dist: pyautogui==0.9.54
17
- Requires-Dist: pytest-asyncio==0.23.6
18
- Requires-Dist: pytest==8.3.3
17
+ Requires-Dist: pywin32>=306; sys_platform == 'win32'
19
18
  Requires-Dist: pywinauto; sys_platform == 'win32'
20
- Requires-Dist: ruff==0.6.7
19
+ Requires-Dist: requests>=2.0
21
20
  Requires-Dist: screeninfo
22
21
  Requires-Dist: streamlit>=1.38.0
23
22
  Requires-Dist: textdistance
24
23
  Requires-Dist: uiautomation; sys_platform == 'win32'
25
24
  Provides-Extra: dev
25
+ Requires-Dist: pre-commit>=3.8.0; extra == 'dev'
26
26
  Requires-Dist: pytest-asyncio>=0.23.6; extra == 'dev'
27
27
  Requires-Dist: pytest>=8.3.3; extra == 'dev'
28
28
  Requires-Dist: ruff>=0.6.7; extra == 'dev'
@@ -1,11 +1,12 @@
1
1
  computer_use_ootb_internal/README.md,sha256=FxpW95lyub2iX73ZDfK6ML7SdEKg060H5I6Grub7li4,31
2
2
  computer_use_ootb_internal/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
3
- computer_use_ootb_internal/app_teachmode.py,sha256=-PQVCIi-6mkJ8vbyEenTPnCDYgu7R33ci_kFY7Ccfbw,18771
3
+ computer_use_ootb_internal/app_teachmode.py,sha256=U0vHAEHeDMDioYatLQkzesT1Hy0aI6kKjhAlV86zxfc,17902
4
4
  computer_use_ootb_internal/app_teachmode_gradio.py,sha256=cmFpBrkdlZxOQADWveVdIaaNqaBD8IVs-xNLJogU7F8,7909
5
5
  computer_use_ootb_internal/dependency_check.py,sha256=y8RMEP6RXQzTgU1MS_1piBLtz4J-Hfn9RjUZg59dyvo,1333
6
+ computer_use_ootb_internal/guard_service.py,sha256=bTzBQMnHYaUXBwCWcnn0cXBPJyKl26-LVRIRB-fiRzo,20558
6
7
  computer_use_ootb_internal/requirements-lite.txt,sha256=5DAHomz4A_P2BmTIXNkNqkHbnIF0AyZ4_1XAlb1LaYs,290
7
8
  computer_use_ootb_internal/run_teachmode_ootb_args.py,sha256=7Dj0iY4GG7P03tRKYJ2x9Yvt-PE-b7uyjCAed3SaF3Y,7086
8
- computer_use_ootb_internal/startup_utils.py,sha256=JXu_13YuVdCUVJHgLoSyQV-tHy7qGEYgyPOyVgoxva4,9334
9
+ computer_use_ootb_internal/service_manager.py,sha256=sesbSUBBqm6W77Dykaw-HGARU-yNdK9DqOHPiR2TbkE,6920
9
10
  computer_use_ootb_internal/computer_use_demo/animation/click_animation.py,sha256=tP1gsayFy-CKk10UlrE9RlexwlHWiHQUe5Ogg4vQvSg,3234
10
11
  computer_use_ootb_internal/computer_use_demo/animation/icons8-select-cursor-transparent-96.gif,sha256=4LfwsfFQnREXrNRs32aJU2jO65JXianJoL_8q7-8elg,30966
11
12
  computer_use_ootb_internal/computer_use_demo/animation/test_animation.py,sha256=2R1u98OLKYalSZ5nt5vvyZ71FL5R5vLv-n8zM8jVdV8,1183
@@ -31,7 +32,9 @@ computer_use_ootb_internal/computer_use_demo/tools/computer_marbot.py,sha256=zZu
31
32
  computer_use_ootb_internal/computer_use_demo/tools/edit.py,sha256=b0PwUitxckHCQqFP3ZwlthWdqNkn7WETeTHeB6-o98c,11486
32
33
  computer_use_ootb_internal/computer_use_demo/tools/run.py,sha256=xhXdnBK1di9muaO44CEirL9hpGy3NmKbjfMpyeVmn8Y,1595
33
34
  computer_use_ootb_internal/computer_use_demo/tools/screen_capture.py,sha256=L8qfvtUkPPQGt92N-2Zfw5ZTDBzLsDps39uMnX3_uSA,6857
34
- computer_use_ootb_internal-0.0.110.dist-info/METADATA,sha256=VApESAleeSZz-CoNCaWHkEC9WFHl-Nb5qfa6MUC3bIw,937
35
- computer_use_ootb_internal-0.0.110.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
- computer_use_ootb_internal-0.0.110.dist-info/entry_points.txt,sha256=XOxhaseEddM9LaMoWFupcavxn5jMU7kMrLyrmcfc_bQ,255
37
- computer_use_ootb_internal-0.0.110.dist-info/RECORD,,
35
+ computer_use_ootb_internal/preparation/__init__.py,sha256=AgtGHcBpiTkxJjF0xwcs3yyQ6SyUvhL3G0vD2XO-zJw,63
36
+ computer_use_ootb_internal/preparation/star_rail_prepare.py,sha256=s1VWszcTnJAKxqCHFlaOEwPkqVSrkiFx_yKpWSnSbHs,2649
37
+ computer_use_ootb_internal-0.0.112.dist-info/METADATA,sha256=P4zVajg7g2i1wpHYk98DU4M9JawiqWp4mKz7crW9qSM,993
38
+ computer_use_ootb_internal-0.0.112.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
+ computer_use_ootb_internal-0.0.112.dist-info/entry_points.txt,sha256=bXfyAU_qq-G1EiEgAQEioXvgEdRCFxaTooqdDD9Y4OA,258
40
+ computer_use_ootb_internal-0.0.112.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ computer-use-ootb-internal = computer_use_ootb_internal.app_teachmode:main
3
+ ootb-install-service = computer_use_ootb_internal.service_manager:install_and_start
4
+ ootb-remove-service = computer_use_ootb_internal.service_manager:stop_and_remove
@@ -1,194 +0,0 @@
1
- import sys
2
- import os
3
- import winreg
4
- import ctypes
5
- import platform
6
- import shutil
7
- import subprocess
8
- import pathlib
9
-
10
- REG_PATH = r"Software\Microsoft\Windows\CurrentVersion\Run"
11
- APP_NAME = "OOTBLite" # Or a more specific name for your app's startup entry
12
- # The package name as installed by pip
13
- PACKAGE_NAME = "computer-use-ootb-internal"
14
- # The main module to run at startup
15
- STARTUP_MODULE = "computer_use_ootb_internal.app_teachmode"
16
- # Name for the scheduled task
17
- TASK_NAME = "OOTBLite_AutoUpdate"
18
-
19
- def is_admin():
20
- """Check if the script is running with administrative privileges."""
21
- try:
22
- return ctypes.windll.shell32.IsUserAnAdmin()
23
- except:
24
- return False
25
-
26
- def get_python_executable():
27
- """Gets the quoted path to the current python executable."""
28
- python_exe = sys.executable
29
- if " " in python_exe and not python_exe.startswith('"'):
30
- python_exe = f'"{python_exe}"'
31
- return python_exe
32
-
33
- def get_pip_executable():
34
- """Tries to locate the pip executable in the same environment."""
35
- python_path = pathlib.Path(sys.executable)
36
- # Common location is ../Scripts/pip.exe relative to python.exe
37
- pip_path = python_path.parent / "Scripts" / "pip.exe"
38
- if pip_path.exists():
39
- # Quote if necessary
40
- pip_exe = str(pip_path)
41
- if " " in pip_exe and not pip_exe.startswith('"'):
42
- pip_exe = f'"{pip_exe}"'
43
- return pip_exe
44
- else:
45
- # Fallback: try using 'python -m pip'
46
- print("Warning: pip.exe not found in Scripts directory. Falling back to 'python -m pip'.", file=sys.stderr)
47
- return f"{get_python_executable()} -m pip"
48
-
49
- def run_powershell_command(command):
50
- """Executes a PowerShell command and handles output/errors."""
51
- try:
52
- # Use powershell.exe - it's more universally available than pwsh.exe
53
- # capture_output=True suppresses output unless there's an error
54
- # text=True decodes output/error streams
55
- # check=True raises CalledProcessError on non-zero exit codes
56
- result = subprocess.run(
57
- ["powershell.exe", "-NoProfile", "-Command", command],
58
- capture_output=True, text=True, check=True, encoding='utf-8'
59
- )
60
- print(f"PowerShell command executed successfully.")
61
- if result.stdout:
62
- print("Output:\n", result.stdout)
63
- return True
64
- except FileNotFoundError:
65
- print("Error: 'powershell.exe' not found. Cannot manage scheduled tasks.", file=sys.stderr)
66
- return False
67
- except subprocess.CalledProcessError as e:
68
- print(f"Error executing PowerShell command:", file=sys.stderr)
69
- print(f" Command: {e.cmd}", file=sys.stderr)
70
- print(f" Exit Code: {e.returncode}", file=sys.stderr)
71
- print(f" Stderr: {e.stderr}", file=sys.stderr)
72
- print(f" Stdout: {e.stdout}", file=sys.stderr)
73
- return False
74
- except Exception as e:
75
- print(f"An unexpected error occurred running PowerShell: {e}", file=sys.stderr)
76
- return False
77
-
78
-
79
- def configure_startup():
80
- """Adds the application to Windows startup and sets up auto-update task."""
81
- if platform.system() != "Windows":
82
- print("Error: This utility is only for Windows.", file=sys.stderr)
83
- sys.exit(1)
84
-
85
- if not is_admin():
86
- print("Error: This utility requires administrative privileges.", file=sys.stderr)
87
- print("Please run this command from an Administrator Command Prompt or PowerShell.", file=sys.stderr)
88
- sys.exit(1)
89
-
90
- # 1. Configure Registry for Startup
91
- print("Configuring registry for application startup...")
92
- python_exe = get_python_executable()
93
- startup_command = f'{python_exe} -m {STARTUP_MODULE}'
94
- try:
95
- key = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, REG_PATH)
96
- winreg.SetValueEx(key, APP_NAME, 0, winreg.REG_SZ, startup_command)
97
- winreg.CloseKey(key)
98
- print(f"Success: Registry key HKLM\{REG_PATH}\{APP_NAME} set.")
99
- print(f" Command: {startup_command}")
100
- except OSError as e:
101
- print(f"Error: Failed to set registry key HKLM\{REG_PATH}\{APP_NAME}", file=sys.stderr)
102
- print(f"Details: {e}", file=sys.stderr)
103
- # Continue to task setup even if registry fails? Or exit? Let's exit for now.
104
- sys.exit(1)
105
- except Exception as e:
106
- print(f"An unexpected error occurred setting registry key: {e}", file=sys.stderr)
107
- sys.exit(1)
108
-
109
- # 2. Configure Scheduled Task for Auto-Update
110
- print("\nConfiguring scheduled task for automatic updates...")
111
- pip_command = get_pip_executable()
112
- if not pip_command:
113
- print("Error: Could not determine pip command. Skipping scheduled task setup.", file=sys.stderr)
114
- sys.exit(1) # Exit if we can't find pip
115
-
116
- update_command_args = f'install --upgrade --no-cache-dir {PACKAGE_NAME}'
117
- # Need to handle quoting args for PowerShell if pip_command includes python.exe
118
- pip_exe_path = pip_command.split()[0]
119
- if "-m pip" in pip_command:
120
- ps_args = f"-m pip {update_command_args}"
121
- else:
122
- ps_args = update_command_args
123
-
124
- # PowerShell commands to create the task
125
- # Define Action, Trigger, Principal, Settings, and then Register
126
- # Note: Escaping quotes for PowerShell within Python string
127
- # Ensure executable path and arguments are properly quoted for PowerShell
128
- # Use triple-double quotes for f-strings containing single-quoted PowerShell strings
129
- action = f"""$Action = New-ScheduledTaskAction -Execute '{pip_exe_path}' -Argument '{ps_args.replace("'", "''")}'""" # Escape single quotes in args for PS string
130
- # Trigger: Daily, repeat every hour indefinitely
131
- trigger = f"""$Trigger = New-ScheduledTaskTrigger -Daily -At 3am; $Trigger.Repetition.Interval = 'PT1H'; $Trigger.Repetition.Duration = 'P10675199D'""" # Using large duration for 'indefinitely'
132
- # Principal: Run as SYSTEM user
133
- principal = f"""$Principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest"""
134
- # Settings: Allow start if on batteries, don't stop if goes off batteries (adjust as needed)
135
- settings = f"""$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable""" # Added StartWhenAvailable
136
-
137
- # Register Task: Use -Force to overwrite if it exists
138
- description = f"Hourly check for {PACKAGE_NAME} updates."
139
- # Escape single quotes in description just in case PACKAGE_NAME contains them
140
- escaped_description = description.replace("'", "''")
141
- register_command = f"""{action}; {trigger}; {principal}; {settings}; Register-ScheduledTask -TaskName '{TASK_NAME}' -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings -Force -Description '{escaped_description}'"""
142
-
143
- print(f"Attempting to create/update scheduled task '{TASK_NAME}'...")
144
- if run_powershell_command(register_command):
145
- print(f"Success: Scheduled task '{TASK_NAME}' created/updated.")
146
- print(f" Task Action: {pip_exe_path} {ps_args}")
147
- else:
148
- print(f"Error: Failed to configure scheduled task '{TASK_NAME}'. Please check PowerShell errors above.", file=sys.stderr)
149
- # Decide if failure here is critical
150
- # sys.exit(1)
151
-
152
-
153
- def remove_startup():
154
- """Removes the application from Windows startup and removes auto-update task."""
155
- if platform.system() != "Windows":
156
- print("Error: This utility is only for Windows.", file=sys.stderr)
157
- sys.exit(1)
158
-
159
- if not is_admin():
160
- print("Error: This utility requires administrative privileges.", file=sys.stderr)
161
- print("Please run this command from an Administrator Command Prompt or PowerShell.", file=sys.stderr)
162
- sys.exit(1)
163
-
164
- # 1. Remove Registry Key
165
- print("Removing registry key...")
166
- try:
167
- key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, REG_PATH, 0, winreg.KEY_WRITE)
168
- winreg.DeleteValue(key, APP_NAME)
169
- winreg.CloseKey(key)
170
- print(f"Success: Registry key HKLM\{REG_PATH}\{APP_NAME} removed.")
171
- except FileNotFoundError:
172
- print(f"Info: Registry key HKLM\{REG_PATH}\{APP_NAME} not found. No action taken.")
173
- except OSError as e:
174
- print(f"Error: Failed to delete registry key HKLM\{REG_PATH}\{APP_NAME}", file=sys.stderr)
175
- print(f"Details: {e}", file=sys.stderr)
176
- # Continue to task removal
177
- except Exception as e:
178
- print(f"An unexpected error occurred removing registry key: {e}", file=sys.stderr)
179
- # Continue to task removal
180
-
181
- # 2. Remove Scheduled Task
182
- print(f"\nRemoving scheduled task '{TASK_NAME}'...")
183
- # Use -ErrorAction SilentlyContinue for Unregister-ScheduledTask if it might not exist
184
- # Use triple-double quotes for f-string
185
- unregister_command = f"""Unregister-ScheduledTask -TaskName '{TASK_NAME}' -Confirm:$false -ErrorAction SilentlyContinue"""
186
-
187
- if run_powershell_command(unregister_command):
188
- print(f"Success: Scheduled task '{TASK_NAME}' removed (if it existed).")
189
- else:
190
- print(f"Error: Failed to remove scheduled task '{TASK_NAME}'. Please check PowerShell errors above.", file=sys.stderr)
191
-
192
- if __name__ == '__main__':
193
- print("This script provides startup and auto-update configuration utilities.")
194
- print("Use 'ootb-configure-startup' or 'ootb-remove-startup' as Administrator after installation.")
@@ -1,4 +0,0 @@
1
- [console_scripts]
2
- computer-use-ootb-internal = computer_use_ootb_internal.app_teachmode:main
3
- ootb-configure-startup = computer_use_ootb_internal.startup_utils:configure_startup
4
- ootb-remove-startup = computer_use_ootb_internal.startup_utils:remove_startup