@codesys/mcp-toolkit 1.2.0 → 1.2.4

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.
@@ -0,0 +1,168 @@
1
+ import sys, os, json, time, tempfile
2
+
3
+ # Define markers for script results
4
+ SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
5
+ SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
6
+
7
+ # Session state file and pipes
8
+ SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
9
+ SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
10
+ COMMAND_PIPE = os.path.join(SESSION_DIR, "command_pipe")
11
+ RESULT_PIPE = os.path.join(SESSION_DIR, "result_pipe")
12
+
13
+ def check_session_status():
14
+ """Check the status of a CODESYS session by reading the state file"""
15
+ try:
16
+ # Check if session directory exists
17
+ if not os.path.exists(SESSION_DIR):
18
+ return {
19
+ "session_exists": False,
20
+ "active": False,
21
+ "message": "No session directory found"
22
+ }
23
+
24
+ # Check if state file exists
25
+ if not os.path.exists(SESSION_STATE_FILE):
26
+ return {
27
+ "session_exists": True,
28
+ "active": False,
29
+ "message": "Session directory exists but no state file found"
30
+ }
31
+
32
+ # Read the state file
33
+ with open(SESSION_STATE_FILE, 'r') as f:
34
+ session_state = json.load(f)
35
+
36
+ # Check if pipes exist
37
+ pipes_exist = os.path.exists(COMMAND_PIPE) and os.path.exists(RESULT_PIPE)
38
+
39
+ # Get session duration if available
40
+ start_time = session_state.get("start_time", 0)
41
+ if start_time > 0:
42
+ current_time = time.time()
43
+ duration_secs = int(current_time - start_time)
44
+ hours, remainder = divmod(duration_secs, 3600)
45
+ minutes, seconds = divmod(remainder, 60)
46
+ duration = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
47
+ else:
48
+ duration = "unknown"
49
+
50
+ # Last update time
51
+ last_update = session_state.get("last_update", 0)
52
+ last_update_str = "never"
53
+ if last_update > 0:
54
+ seconds_since_update = int(time.time() - last_update)
55
+ if seconds_since_update < 60:
56
+ last_update_str = f"{seconds_since_update} seconds ago"
57
+ elif seconds_since_update < 3600:
58
+ last_update_str = f"{seconds_since_update // 60} minutes ago"
59
+ else:
60
+ last_update_str = f"{seconds_since_update // 3600} hours ago"
61
+
62
+ return {
63
+ "session_exists": True,
64
+ "active": session_state.get("active", False),
65
+ "project_open": session_state.get("project_open", False),
66
+ "project_name": session_state.get("project_name", "unknown"),
67
+ "project_path": session_state.get("project_path", "unknown"),
68
+ "duration": duration,
69
+ "pipes_exist": pipes_exist,
70
+ "last_update": last_update_str,
71
+ "message": "Session information retrieved successfully"
72
+ }
73
+ except Exception as e:
74
+ return {
75
+ "session_exists": True,
76
+ "active": False,
77
+ "error": str(e),
78
+ "message": f"Error reading session state: {e}"
79
+ }
80
+
81
+ def ping_session():
82
+ """Attempt to ping the session with a simple command to verify it's responsive"""
83
+ try:
84
+ if not os.path.exists(COMMAND_PIPE) or not os.path.exists(RESULT_PIPE):
85
+ return False
86
+
87
+ ping_command = {
88
+ "id": f"ping_{time.time()}",
89
+ "type": "execute_script",
90
+ "script": "print('SCRIPT_SUCCESS: Ping successful')"
91
+ }
92
+
93
+ # Set a timeout for the ping operation
94
+ timeout = 5 # seconds
95
+
96
+ # Use non-blocking I/O to handle timeouts
97
+ import select
98
+
99
+ # Send the ping command
100
+ with open(COMMAND_PIPE, 'w') as cmd_pipe:
101
+ cmd_pipe.write(json.dumps(ping_command))
102
+
103
+ # Wait for the result pipe to be readable with timeout
104
+ res_fd = os.open(RESULT_PIPE, os.O_RDONLY | os.O_NONBLOCK)
105
+ ready, _, _ = select.select([res_fd], [], [], timeout)
106
+
107
+ if not ready:
108
+ os.close(res_fd)
109
+ return False
110
+
111
+ # Read the result
112
+ result_json = os.read(res_fd, 4096).decode('utf-8').strip()
113
+ os.close(res_fd)
114
+
115
+ if result_json:
116
+ try:
117
+ result = json.loads(result_json)
118
+ return result.get("success", False)
119
+ except json.JSONDecodeError:
120
+ return False
121
+ else:
122
+ return False
123
+
124
+ except Exception as e:
125
+ print(f"Error pinging session: {e}")
126
+ return False
127
+
128
+ def main():
129
+ try:
130
+ # Get session status
131
+ status = check_session_status()
132
+
133
+ # Print status information
134
+ print(f"Session Exists: {status.get('session_exists', False)}")
135
+ print(f"Session Active: {status.get('active', False)}")
136
+
137
+ # If session exists, print more details
138
+ if status.get("session_exists", False):
139
+ if status.get("active", False):
140
+ # Try to ping the session
141
+ is_responsive = ping_session()
142
+ print(f"Session Responsive: {is_responsive}")
143
+
144
+ # Print project information if available
145
+ if "project_open" in status:
146
+ print(f"Project Open: {status['project_open']}")
147
+ if "project_name" in status:
148
+ print(f"Project Name: {status['project_name']}")
149
+ if "project_path" in status:
150
+ print(f"Project Path: {status['project_path']}")
151
+ if "duration" in status:
152
+ print(f"Session Duration: {status['duration']}")
153
+ if "last_update" in status:
154
+ print(f"Last Activity: {status['last_update']}")
155
+ else:
156
+ print("Session is not active (stopped or crashed)")
157
+
158
+ # Print the final result marker
159
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session status check complete")
160
+ sys.exit(0)
161
+
162
+ except Exception as e:
163
+ print(f"Error checking session status: {e}")
164
+ print(f"{SCRIPT_ERROR_MARKER}: {str(e)}")
165
+ sys.exit(1)
166
+
167
+ if __name__ == "__main__":
168
+ main()
@@ -0,0 +1,104 @@
1
+ import sys, scriptengine as script_engine, os, traceback
2
+
3
+ # Import ensure_project_open function from the template
4
+ # This will be prepended by the script loader
5
+ def ensure_project_open(target_project_path):
6
+ # Function implementation will be included from ensure_project_open.py
7
+ pass
8
+
9
+ # Import find_object_by_path function from the template
10
+ # This will be prepended by the script loader
11
+ def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
12
+ # Function implementation will be included from find_object_by_path.py
13
+ pass
14
+
15
+ POU_FULL_PATH = "{POU_FULL_PATH}" # Expecting format like "Application/MyPOU" or "Folder/SubFolder/MyPOU"
16
+ DECLARATION_CONTENT = """{DECLARATION_CONTENT}"""
17
+ IMPLEMENTATION_CONTENT = """{IMPLEMENTATION_CONTENT}"""
18
+
19
+ try:
20
+ print("DEBUG: set_pou_code script: POU_FULL_PATH='%s', Project='%s'" % (POU_FULL_PATH, PROJECT_FILE_PATH))
21
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
22
+ if not POU_FULL_PATH: raise ValueError("POU full path empty.")
23
+
24
+ # Find the target POU/Method/Property object
25
+ target_object = find_object_by_path_robust(primary_project, POU_FULL_PATH, "target object")
26
+ if not target_object: raise ValueError("Target object not found using path: %s" % POU_FULL_PATH)
27
+
28
+ target_name = getattr(target_object, 'get_name', lambda: POU_FULL_PATH)()
29
+ print("DEBUG: Found target object: %s" % target_name)
30
+
31
+ # --- Set Declaration Part ---
32
+ declaration_updated = False
33
+ # Check if the content is actually provided (might be None/empty if only impl is set)
34
+ has_declaration_content = 'DECLARATION_CONTENT' in locals() or 'DECLARATION_CONTENT' in globals()
35
+ if has_declaration_content and DECLARATION_CONTENT is not None: # Check not None
36
+ if hasattr(target_object, 'textual_declaration'):
37
+ decl_obj = target_object.textual_declaration
38
+ if decl_obj and hasattr(decl_obj, 'replace'):
39
+ try:
40
+ print("DEBUG: Accessing textual_declaration...")
41
+ decl_obj.replace(DECLARATION_CONTENT)
42
+ print("DEBUG: Set declaration text using replace().")
43
+ declaration_updated = True
44
+ except Exception as decl_err:
45
+ print("ERROR: Failed to set declaration text: %s" % decl_err)
46
+ traceback.print_exc() # Print stack trace for detailed error
47
+ else:
48
+ print("WARN: Target '%s' textual_declaration attribute is None or does not have replace(). Skipping declaration update." % target_name)
49
+ else:
50
+ print("WARN: Target '%s' does not have textual_declaration attribute. Skipping declaration update." % target_name)
51
+ else:
52
+ print("DEBUG: Declaration content not provided or is None. Skipping declaration update.")
53
+
54
+
55
+ # --- Set Implementation Part ---
56
+ implementation_updated = False
57
+ has_implementation_content = 'IMPLEMENTATION_CONTENT' in locals() or 'IMPLEMENTATION_CONTENT' in globals()
58
+ if has_implementation_content and IMPLEMENTATION_CONTENT is not None: # Check not None
59
+ if hasattr(target_object, 'textual_implementation'):
60
+ impl_obj = target_object.textual_implementation
61
+ if impl_obj and hasattr(impl_obj, 'replace'):
62
+ try:
63
+ print("DEBUG: Accessing textual_implementation...")
64
+ impl_obj.replace(IMPLEMENTATION_CONTENT)
65
+ print("DEBUG: Set implementation text using replace().")
66
+ implementation_updated = True
67
+ except Exception as impl_err:
68
+ print("ERROR: Failed to set implementation text: %s" % impl_err)
69
+ traceback.print_exc() # Print stack trace for detailed error
70
+ else:
71
+ print("WARN: Target '%s' textual_implementation attribute is None or does not have replace(). Skipping implementation update." % target_name)
72
+ else:
73
+ print("WARN: Target '%s' does not have textual_implementation attribute. Skipping implementation update." % target_name)
74
+ else:
75
+ print("DEBUG: Implementation content not provided or is None. Skipping implementation update.")
76
+
77
+
78
+ # --- SAVE THE PROJECT TO PERSIST THE CODE CHANGE ---
79
+ # Only save if something was actually updated to avoid unnecessary saves
80
+ if declaration_updated or implementation_updated:
81
+ try:
82
+ print("DEBUG: Saving Project (after code change)...")
83
+ primary_project.save() # Save the overall project file
84
+ print("DEBUG: Project saved successfully after code change.")
85
+ except Exception as save_err:
86
+ print("ERROR: Failed to save Project after setting code: %s" % save_err)
87
+ detailed_error = traceback.format_exc()
88
+ error_message = "Error saving Project after code change for '%s': %s\\n%s" % (target_name, save_err, detailed_error)
89
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
90
+ else:
91
+ print("DEBUG: No code parts were updated, skipping project save.")
92
+ # --- END SAVING ---
93
+
94
+ print("Code Set For: %s" % target_name)
95
+ print("Path: %s" % POU_FULL_PATH)
96
+ print("SCRIPT_SUCCESS: Declaration and/or implementation set successfully.")
97
+ sys.exit(0)
98
+
99
+ except Exception as e:
100
+ detailed_error = traceback.format_exc()
101
+ error_message = "Error setting code for object '%s' in project '%s': %s\\n%s" % (POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
102
+ print(error_message)
103
+ print("SCRIPT_ERROR: %s" % error_message)
104
+ sys.exit(1)
@@ -0,0 +1,240 @@
1
+ import sys, os, threading, json, time, tempfile
2
+ import scriptengine as script_engine
3
+
4
+ # Define markers for script results
5
+ SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
6
+ SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
7
+
8
+ # Session state file and pipes
9
+ SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
10
+ SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
11
+ COMMAND_PIPE = os.path.join(SESSION_DIR, "command_pipe")
12
+ RESULT_PIPE = os.path.join(SESSION_DIR, "result_pipe")
13
+
14
+ def setup_session_dir():
15
+ """Create the session directory and pipes if they don't exist"""
16
+ try:
17
+ # Create session directory
18
+ if not os.path.exists(SESSION_DIR):
19
+ os.makedirs(SESSION_DIR)
20
+
21
+ # Create named pipes if they don't exist
22
+ if not os.path.exists(COMMAND_PIPE):
23
+ os.mkfifo(COMMAND_PIPE)
24
+
25
+ if not os.path.exists(RESULT_PIPE):
26
+ os.mkfifo(RESULT_PIPE)
27
+
28
+ # Initialize session state
29
+ session_state = {
30
+ "active": True,
31
+ "start_time": time.time(),
32
+ "project_open": False,
33
+ "project_name": "",
34
+ "project_path": ""
35
+ }
36
+
37
+ with open(SESSION_STATE_FILE, 'w') as f:
38
+ json.dump(session_state, f)
39
+
40
+ return True
41
+ except Exception as e:
42
+ print(f"Failed to set up session directory: {e}")
43
+ print(f"{SCRIPT_ERROR_MARKER}: Session setup failed - {str(e)}")
44
+ return False
45
+
46
+ def update_session_state():
47
+ """Update the session state file with current CODESYS state"""
48
+ try:
49
+ # Get current state
50
+ project_open = False
51
+ project_name = "No project open"
52
+ project_path = "N/A"
53
+
54
+ primary_project = script_engine.projects.primary
55
+ if primary_project:
56
+ project_open = True
57
+ try:
58
+ project_path = os.path.normcase(os.path.abspath(primary_project.path))
59
+ try:
60
+ project_name = primary_project.get_name()
61
+ if not project_name:
62
+ project_name = f"Unnamed (path: {os.path.basename(project_path)})"
63
+ except:
64
+ project_name = f"Unnamed (path: {os.path.basename(project_path)})"
65
+ except Exception as e_path:
66
+ project_path = f"N/A (Error: {e_path})"
67
+ project_name = "Unnamed (Path Error)"
68
+
69
+ # Read current state file
70
+ with open(SESSION_STATE_FILE, 'r') as f:
71
+ session_state = json.load(f)
72
+
73
+ # Update state
74
+ session_state["project_open"] = project_open
75
+ session_state["project_name"] = project_name
76
+ session_state["project_path"] = project_path
77
+ session_state["last_update"] = time.time()
78
+
79
+ # Write updated state
80
+ with open(SESSION_STATE_FILE, 'w') as f:
81
+ json.dump(session_state, f)
82
+
83
+ return True
84
+ except Exception as e:
85
+ print(f"Failed to update session state: {e}")
86
+ return False
87
+
88
+ def execute_command(command_data):
89
+ """Execute a command from the command pipe"""
90
+ try:
91
+ cmd_type = command_data.get("type", "")
92
+ script = command_data.get("script", "")
93
+ cmd_id = command_data.get("id", "unknown")
94
+
95
+ result = {
96
+ "id": cmd_id,
97
+ "success": False,
98
+ "output": "",
99
+ "error": ""
100
+ }
101
+
102
+ if cmd_type == "execute_script" and script:
103
+ # Execute the script in the current CODESYS context
104
+ try:
105
+ # Capture output by redirecting stdout/stderr
106
+ import io
107
+ from contextlib import redirect_stdout, redirect_stderr
108
+
109
+ stdout_buffer = io.StringIO()
110
+ stderr_buffer = io.StringIO()
111
+
112
+ with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
113
+ # Execute the script
114
+ exec(script, globals())
115
+
116
+ # Get the captured output
117
+ stdout_output = stdout_buffer.getvalue()
118
+ stderr_output = stderr_buffer.getvalue()
119
+
120
+ # Check for success/error markers
121
+ if SCRIPT_SUCCESS_MARKER in stdout_output or SCRIPT_SUCCESS_MARKER in stderr_output:
122
+ result["success"] = True
123
+ elif SCRIPT_ERROR_MARKER in stdout_output or SCRIPT_ERROR_MARKER in stderr_output:
124
+ result["success"] = False
125
+ else:
126
+ # No explicit markers, assume success
127
+ result["success"] = True
128
+
129
+ result["output"] = stdout_output
130
+ if stderr_output:
131
+ result["error"] = stderr_output
132
+
133
+ except Exception as e:
134
+ result["success"] = False
135
+ result["error"] = f"Error executing script: {str(e)}"
136
+ result["output"] = f"{SCRIPT_ERROR_MARKER}: {str(e)}"
137
+
138
+ elif cmd_type == "stop":
139
+ # Stop command received
140
+ result["success"] = True
141
+ result["output"] = f"{SCRIPT_SUCCESS_MARKER}: Session stopping"
142
+
143
+ # Update session state to mark as inactive
144
+ with open(SESSION_STATE_FILE, 'r') as f:
145
+ session_state = json.load(f)
146
+
147
+ session_state["active"] = False
148
+ session_state["end_time"] = time.time()
149
+
150
+ with open(SESSION_STATE_FILE, 'w') as f:
151
+ json.dump(session_state, f)
152
+
153
+ # Return the result and signal to stop the loop
154
+ return result, True
155
+
156
+ else:
157
+ result["success"] = False
158
+ result["error"] = f"Unknown command type: {cmd_type}"
159
+ result["output"] = f"{SCRIPT_ERROR_MARKER}: Unknown command type: {cmd_type}"
160
+
161
+ # Update session state after command execution
162
+ update_session_state()
163
+
164
+ return result, False
165
+
166
+ except Exception as e:
167
+ return {
168
+ "id": command_data.get("id", "unknown"),
169
+ "success": False,
170
+ "output": f"{SCRIPT_ERROR_MARKER}: Command execution failed",
171
+ "error": str(e)
172
+ }, False
173
+
174
+ def command_loop():
175
+ """Main loop to listen for and execute commands"""
176
+ stop_requested = False
177
+
178
+ print(f"Starting command loop...")
179
+ print(f"Command pipe: {COMMAND_PIPE}")
180
+ print(f"Result pipe: {RESULT_PIPE}")
181
+
182
+ while not stop_requested:
183
+ try:
184
+ # Open the command pipe for reading (blocks until data is available)
185
+ with open(COMMAND_PIPE, 'r') as cmd_pipe:
186
+ # Read the command
187
+ command_json = cmd_pipe.read().strip()
188
+
189
+ if command_json:
190
+ print(f"Received command: {command_json[:100]}...")
191
+ try:
192
+ command_data = json.loads(command_json)
193
+ result, stop_requested = execute_command(command_data)
194
+
195
+ # Write the result to the result pipe
196
+ with open(RESULT_PIPE, 'w') as res_pipe:
197
+ res_pipe.write(json.dumps(result))
198
+
199
+ except json.JSONDecodeError as e:
200
+ # Invalid JSON
201
+ print(f"Invalid command JSON: {e}")
202
+ error_result = {
203
+ "id": "unknown",
204
+ "success": False,
205
+ "output": f"{SCRIPT_ERROR_MARKER}: Invalid command format",
206
+ "error": str(e)
207
+ }
208
+ with open(RESULT_PIPE, 'w') as res_pipe:
209
+ res_pipe.write(json.dumps(error_result))
210
+
211
+ except Exception as e:
212
+ print(f"Error in command loop: {e}")
213
+ # Short delay to prevent CPU spinning in case of repeated errors
214
+ time.sleep(0.5)
215
+
216
+ print("Command loop stopped.")
217
+
218
+ # Main script
219
+ def main():
220
+ try:
221
+ # Setup session directory and pipes
222
+ if not setup_session_dir():
223
+ sys.exit(1)
224
+
225
+ # Update initial session state
226
+ update_session_state()
227
+
228
+ # Start command loop
229
+ print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session started")
230
+ command_loop()
231
+
232
+ print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session stopped cleanly")
233
+ sys.exit(0)
234
+ except Exception as e:
235
+ print(f"Error in session: {e}")
236
+ print(f"{SCRIPT_ERROR_MARKER}: Session failed - {str(e)}")
237
+ sys.exit(1)
238
+
239
+ if __name__ == "__main__":
240
+ main()
@@ -0,0 +1,94 @@
1
+ import sys, os, json, time, tempfile
2
+
3
+ # Define markers for script results
4
+ SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
5
+ SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
6
+
7
+ # Session state file and pipes
8
+ SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
9
+ SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
10
+ COMMAND_PIPE = os.path.join(SESSION_DIR, "command_pipe")
11
+ RESULT_PIPE = os.path.join(SESSION_DIR, "result_pipe")
12
+
13
+ def check_session_active():
14
+ """Check if a session is active by reading the state file"""
15
+ try:
16
+ if not os.path.exists(SESSION_STATE_FILE):
17
+ print(f"No session state file found at {SESSION_STATE_FILE}")
18
+ return False
19
+
20
+ with open(SESSION_STATE_FILE, 'r') as f:
21
+ session_state = json.load(f)
22
+
23
+ return session_state.get("active", False)
24
+ except Exception as e:
25
+ print(f"Error checking session state: {e}")
26
+ return False
27
+
28
+ def send_stop_command():
29
+ """Send the stop command to the running session"""
30
+ try:
31
+ if not os.path.exists(COMMAND_PIPE) or not os.path.exists(RESULT_PIPE):
32
+ print(f"Command or result pipes not found")
33
+ return False
34
+
35
+ stop_command = {
36
+ "id": f"stop_{time.time()}",
37
+ "type": "stop",
38
+ "script": ""
39
+ }
40
+
41
+ # Send the stop command
42
+ with open(COMMAND_PIPE, 'w') as cmd_pipe:
43
+ cmd_pipe.write(json.dumps(stop_command))
44
+
45
+ # Wait for the result
46
+ with open(RESULT_PIPE, 'r') as res_pipe:
47
+ result_json = res_pipe.read().strip()
48
+
49
+ if result_json:
50
+ try:
51
+ result = json.loads(result_json)
52
+ return result.get("success", False)
53
+ except json.JSONDecodeError:
54
+ print(f"Invalid result JSON: {result_json}")
55
+ return False
56
+ else:
57
+ print("No result received")
58
+ return False
59
+
60
+ except Exception as e:
61
+ print(f"Error sending stop command: {e}")
62
+ return False
63
+
64
+ def main():
65
+ try:
66
+ # Check if session is active
67
+ if not check_session_active():
68
+ print(f"{SCRIPT_SUCCESS_MARKER}: No active session found")
69
+ sys.exit(0)
70
+
71
+ # Send stop command
72
+ print("Sending stop command to CODESYS session...")
73
+ if send_stop_command():
74
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session stop command sent successfully")
75
+
76
+ # Give the session a moment to clean up
77
+ time.sleep(1)
78
+
79
+ # Verify session state has been updated
80
+ if not check_session_active():
81
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session stopped successfully")
82
+ else:
83
+ print(f"{SCRIPT_SUCCESS_MARKER}: Stop command accepted, but session may still be shutting down")
84
+ else:
85
+ print(f"{SCRIPT_ERROR_MARKER}: Failed to stop session")
86
+ sys.exit(1)
87
+
88
+ except Exception as e:
89
+ print(f"Error stopping session: {e}")
90
+ print(f"{SCRIPT_ERROR_MARKER}: {str(e)}")
91
+ sys.exit(1)
92
+
93
+ if __name__ == "__main__":
94
+ main()