@codesys/mcp-toolkit 1.0.9 → 1.1.2

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.
package/dist/server.js CHANGED
@@ -2,9 +2,11 @@
2
2
  /**
3
3
  * server.ts
4
4
  * MCP Server for interacting with CODESYS via Python scripting.
5
- * Version: 1.6.9 (Fix disconnect error on shutdown)
6
- * - Removed invalid server.disconnect() calls in SIGINT/SIGTERM handlers.
7
- * - Includes property/method creation tools (v1.6.8).
5
+ * Implements all MCP resources and tools that interact with the CODESYS environment.
6
+ *
7
+ * IMPORTANT: This server receives configuration as parameters from bin.ts,
8
+ * which helps avoid issues with command-line argument passing in different execution environments.
9
+ * (Incorporates script templates from v1.6.9)
8
10
  */
9
11
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
12
  if (k2 === undefined) k2 = k;
@@ -49,1411 +51,1436 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
49
51
  });
50
52
  };
51
53
  Object.defineProperty(exports, "__esModule", { value: true });
52
- console.error(">>> SERVER.TS TOP LEVEL EXECUTION <<<");
54
+ exports.startMcpServer = startMcpServer;
55
+ // --- Import 'os' FIRST ---
56
+ const os = __importStar(require("os"));
57
+ // --- End Import 'os' ---
58
+ // --- STARTUP LOG ---
59
+ console.error(`>>> SERVER.TS TOP LEVEL EXECUTION @ ${new Date().toISOString()} <<<`);
60
+ console.error(`>>> Node: ${process.version}, Platform: ${os.platform()}, Arch: ${os.arch()}`);
61
+ console.error(`>>> Initial CWD: ${process.cwd()}`);
62
+ // --- End Startup Log ---
63
+ // --- Necessary Imports ---
53
64
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
54
65
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
55
66
  const zod_1 = require("zod");
56
- const codesys_interop_1 = require("./codesys_interop"); // Assumes this utility exists and works
57
- const os = __importStar(require("os"));
67
+ const codesys_interop_1 = require("./codesys_interop"); // Assumes this utility exists
58
68
  const path = __importStar(require("path"));
59
- const promises_1 = require("fs/promises"); // For file existence check
60
- // --- Configuration ---
61
- const WORKSPACE_DIR = process.cwd(); // Use current working directory as default workspace
62
- console.error(`Using Workspace Directory: ${WORKSPACE_DIR}`);
63
- // --- CODESYS Configuration - with multiple fallbacks ---
64
- console.error('Command line arguments:', process.argv);
65
- // Get paths with multiple fallback mechanisms
66
- let codesysExePath = process.argv[2] || process.env.CODESYS_PATH;
67
- let codesysProfileName = process.argv[3] || process.env.CODESYS_PROFILE;
68
- // Last-resort defaults
69
- if (!codesysExePath) {
70
- codesysExePath = "C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\Common\\CODESYS.exe";
71
- console.error(`WARNING: Using default CODESYS path: ${codesysExePath}`);
72
- }
73
- if (!codesysProfileName) {
74
- codesysProfileName = "CODESYS V3.5 SP21";
75
- console.error(`WARNING: Using default CODESYS profile: ${codesysProfileName}`);
76
- }
77
- // Sanity check - confirm the path exists if possible
78
- try {
79
- const fs = require('fs');
80
- if (!fs.existsSync(codesysExePath)) {
81
- console.error(`WARNING: CODESYS executable not found at: ${codesysExePath}`);
82
- }
83
- else {
84
- console.error(`Confirmed CODESYS executable exists at: ${codesysExePath}`);
85
- }
86
- }
87
- catch (err) {
88
- console.error(`Error checking CODESYS path: ${err.message}`);
89
- }
90
- console.error('INFO: Using command-line arguments is recommended for production.');
91
- console.error(`Using CODESYS Path: ${codesysExePath}`);
92
- console.error(`Using CODESYS Profile: ${codesysProfileName}`);
93
- console.error(`OS: ${os.platform()} ${os.release()}, Node: ${process.version}, CWD: ${process.cwd()}`);
94
- // --- End CODESYS Configuration ---
95
- // --- Helper Function ---
96
- function fileExists(filePath) {
69
+ const promises_1 = require("fs/promises"); // For file existence check (async)
70
+ // --- Wrap server logic in an exported function ---
71
+ function startMcpServer(config) {
97
72
  return __awaiter(this, void 0, void 0, function* () {
73
+ console.error(`>>> SERVER.TS startMcpServer() CALLED @ ${new Date().toISOString()} <<<`);
74
+ console.error(`>>> Config Received: ${JSON.stringify(config)}`);
75
+ // --- Use config values directly ---
76
+ const WORKSPACE_DIR = config.workspaceDir;
77
+ const codesysExePath = config.codesysPath;
78
+ const codesysProfileName = config.profileName;
79
+ console.error(`SERVER.TS: Using Workspace Directory: ${WORKSPACE_DIR}`);
80
+ console.error(`SERVER.TS: Using CODESYS Path: ${codesysExePath}`);
81
+ console.error(`SERVER.TS: Using CODESYS Profile: ${codesysProfileName}`);
82
+ // --- Sanity check - confirm the path exists if possible ---
83
+ // This helps catch configuration issues early and prevents runtime failures
84
+ console.error(`SERVER.TS: Checking existence of CODESYS executable: ${codesysExePath}`);
98
85
  try {
99
- yield (0, promises_1.stat)(filePath);
100
- return true;
101
- }
102
- catch (error) {
103
- if (error.code === 'ENOENT') {
104
- return false;
86
+ // Using sync check here as it's part of initial setup before async operations start
87
+ const fsChecker = require('fs');
88
+ if (!fsChecker.existsSync(codesysExePath)) {
89
+ console.error(`SERVER.TS ERROR: Determined CODESYS executable path does not exist: ${codesysExePath}`);
90
+ // Consider throwing an error instead of exiting if bin.ts handles the catch
91
+ throw new Error(`CODESYS executable not found at specified path: ${codesysExePath}`);
92
+ // process.exit(1); // Avoid process.exit inside library functions if possible
93
+ }
94
+ else {
95
+ console.error(`SERVER.TS: Confirmed CODESYS executable exists.`);
105
96
  }
106
- throw error;
107
97
  }
108
- });
109
- }
110
- // --- MCP Server Initialization ---
111
- const server = new mcp_js_1.McpServer({
112
- name: "CODESYS Control MCP Server",
113
- version: "1.6.9", // Incremented version
114
- capabilities: { resources: {}, tools: {} }
115
- });
116
- // --- Python Script Templates ---
117
- // (ENSURE_PROJECT_OPEN_PYTHON_SNIPPET remains the same as before)
118
- const ENSURE_PROJECT_OPEN_PYTHON_SNIPPET = `
119
- import sys
120
- import scriptengine as script_engine
121
- import os
122
- import time
123
- import traceback
124
-
125
- # --- Function to ensure the correct project is open ---
126
- MAX_RETRIES = 3
127
- RETRY_DELAY = 2.0 # seconds (use float for time.sleep)
128
-
129
- def ensure_project_open(target_project_path):
130
- print("DEBUG: Ensuring project is open: %s" % target_project_path)
131
- # Normalize target path once
132
- normalized_target_path = os.path.normcase(os.path.abspath(target_project_path))
133
-
134
- for attempt in range(MAX_RETRIES):
135
- print("DEBUG: Ensure project attempt %d/%d for %s" % (attempt + 1, MAX_RETRIES, normalized_target_path))
136
- primary_project = None
137
- try:
138
- # Getting primary project might fail if CODESYS instance is unstable
139
- primary_project = script_engine.projects.primary
140
- except Exception as primary_err:
141
- print("WARN: Error getting primary project: %s. Assuming none." % primary_err)
142
- # traceback.print_exc() # Optional: Print stack trace for this error
143
- primary_project = None
144
-
145
- current_project_path = ""
146
- project_ok = False # Flag to check if target is confirmed primary and accessible
147
-
148
- if primary_project:
149
- try:
150
- # Getting path should be relatively safe if primary_project object exists
151
- current_project_path = os.path.normcase(os.path.abspath(primary_project.path))
152
- print("DEBUG: Current primary project path: %s" % current_project_path)
153
- if current_project_path == normalized_target_path:
154
- # Found the right project as primary, now check if it's usable
155
- print("DEBUG: Target project path matches primary. Checking access...")
156
- try:
157
- # Try a relatively safe operation to confirm object usability
158
- # Getting children count is a reasonable check
159
- _ = len(primary_project.get_children(False))
160
- print("DEBUG: Target project '%s' is primary and accessible." % target_project_path)
161
- project_ok = True
162
- return primary_project # SUCCESS CASE 1: Already open and accessible
163
- except Exception as access_err:
164
- # Project found, but accessing it failed. Might be unstable.
165
- print("WARN: Primary project access check failed for '%s': %s. Will attempt reopen." % (current_project_path, access_err))
166
- # traceback.print_exc() # Optional: Print stack trace
167
- primary_project = None # Force reopen by falling through
168
- else:
169
- # A *different* project is primary
170
- print("DEBUG: Primary project is '%s', not the target '%s'." % (current_project_path, normalized_target_path))
171
- # Consider closing the wrong project if causing issues, but for now, just open target
172
- # try:
173
- # print("DEBUG: Closing incorrect primary project '%s'..." % current_project_path)
174
- # primary_project.close() # Be careful with unsaved changes
175
- # except Exception as close_err:
176
- # print("WARN: Failed to close incorrect primary project: %s" % close_err)
177
- primary_project = None # Force open target project
178
-
179
- except Exception as path_err:
180
- # Failed even to get the path of the supposed primary project
181
- print("WARN: Could not get path of current primary project: %s. Assuming not the target." % path_err)
182
- # traceback.print_exc() # Optional: Print stack trace
183
- primary_project = None # Force open target project
184
-
185
- # If target project not confirmed as primary and accessible, attempt to open/reopen
186
- if not project_ok:
187
- # Log clearly whether we are opening initially or reopening
188
- if primary_project is None and current_project_path == "":
189
- print("DEBUG: No primary project detected. Attempting to open target: %s" % target_project_path)
190
- elif primary_project is None and current_project_path != "":
191
- print("DEBUG: Primary project was '%s' but failed access check or needed close. Attempting to open target: %s" % (current_project_path, target_project_path))
192
- else: # Includes cases where wrong project was open
193
- print("DEBUG: Target project not primary or initial check failed. Attempting to open/reopen: %s" % target_project_path)
194
-
195
- try:
196
- # Set flags for silent opening, handle potential attribute errors
197
- update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
198
- # try:
199
- # update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
200
- # except AttributeError:
201
- # print("WARN: VersionUpdateFlags not found, using integer flags for open (1 | 2 = 3).")
202
- # update_mode = 3 # 1=NoUpdates, 2=SilentMode
203
-
204
- opened_project = None
205
- try:
206
- # The actual open call
207
- print("DEBUG: Calling script_engine.projects.open('%s', update_flags=%s)..." % (target_project_path, update_mode))
208
- opened_project = script_engine.projects.open(target_project_path, update_flags=update_mode)
209
-
210
- if not opened_project:
211
- # This is a critical failure if open returns None without exception
212
- print("ERROR: projects.open returned None for %s on attempt %d" % (target_project_path, attempt + 1))
213
- # Allow retry loop to continue
214
- else:
215
- # Open call returned *something*, let's verify
216
- print("DEBUG: projects.open call returned an object for: %s" % target_project_path)
217
- print("DEBUG: Pausing for stabilization after open...")
218
- time.sleep(RETRY_DELAY) # Give CODESYS time
219
- # Re-verify: Is the project now primary and accessible?
220
- recheck_primary = None
221
- try: recheck_primary = script_engine.projects.primary
222
- except Exception as recheck_primary_err: print("WARN: Error getting primary project after reopen: %s" % recheck_primary_err)
223
-
224
- if recheck_primary:
225
- recheck_path = ""
226
- try: # Getting path might fail
227
- recheck_path = os.path.normcase(os.path.abspath(recheck_primary.path))
228
- except Exception as recheck_path_err:
229
- print("WARN: Failed to get path after reopen: %s" % recheck_path_err)
230
-
231
- if recheck_path == normalized_target_path:
232
- print("DEBUG: Target project confirmed as primary after reopening.")
233
- try: # Final sanity check
234
- _ = len(recheck_primary.get_children(False))
235
- print("DEBUG: Reopened project basic access confirmed.")
236
- return recheck_primary # SUCCESS CASE 2: Successfully opened/reopened
237
- except Exception as access_err_reopen:
238
- print("WARN: Reopened project (%s) basic access check failed: %s." % (normalized_target_path, access_err_reopen))
239
- # traceback.print_exc() # Optional
240
- # Allow retry loop to continue
241
- else:
242
- print("WARN: Different project is primary after reopening! Expected '%s', got '%s'." % (normalized_target_path, recheck_path))
243
- # Allow retry loop to continue, maybe it fixes itself
244
- else:
245
- print("WARN: No primary project found after reopening attempt %d!" % (attempt+1))
246
- # Allow retry loop to continue
247
-
248
- except Exception as open_err:
249
- # Catch errors during the open call itself
250
- print("ERROR: Exception during projects.open call on attempt %d: %s" % (attempt + 1, open_err))
251
- traceback.print_exc() # Crucial for diagnosing open failures
252
- # Allow retry loop to continue
253
-
254
- except Exception as outer_open_err:
255
- # Catch errors in the flag setup etc.
256
- print("ERROR: Unexpected error during open setup/logic attempt %d: %s" % (attempt + 1, outer_open_err))
257
- traceback.print_exc()
258
-
259
- # If we didn't return successfully in this attempt, wait before retrying
260
- if attempt < MAX_RETRIES - 1:
261
- print("DEBUG: Ensure project attempt %d did not succeed. Waiting %f seconds..." % (attempt + 1, RETRY_DELAY))
262
- time.sleep(RETRY_DELAY)
263
- else: # Last attempt failed
264
- print("ERROR: Failed all ensure_project_open attempts for %s." % normalized_target_path)
265
-
266
-
267
- # If all retries fail after the loop
268
- raise RuntimeError("Failed to ensure project '%s' is open and accessible after %d attempts." % (target_project_path, MAX_RETRIES))
269
- # --- End of function ---
270
-
271
- # Placeholder for the project file path (must be set in scripts using this snippet)
272
- PROJECT_FILE_PATH = r"{PROJECT_FILE_PATH}"
98
+ catch (err) {
99
+ console.error(`SERVER.TS ERROR: Error checking CODESYS path existence: ${err.message}`);
100
+ throw err; // Rethrow the error to be caught by the caller (bin.ts)
101
+ // process.exit(1);
102
+ }
103
+ // --- End Configuration Handling ---
104
+ // --- Helper Function (fileExists - async version) ---
105
+ function fileExists(filePath) {
106
+ return __awaiter(this, void 0, void 0, function* () {
107
+ try {
108
+ yield (0, promises_1.stat)(filePath);
109
+ return true;
110
+ }
111
+ catch (error) {
112
+ if (error.code === 'ENOENT') {
113
+ return false; // File does not exist
114
+ }
115
+ throw error; // Other error
116
+ }
117
+ });
118
+ }
119
+ // --- End Helper Function ---
120
+ // --- MCP Server Initialization ---
121
+ console.error("SERVER.TS: Initializing McpServer...");
122
+ const server = new mcp_js_1.McpServer({
123
+ name: "CODESYS Control MCP Server",
124
+ version: "1.7.1", // Update version as needed
125
+ capabilities: { resources: {}, tools: {} }
126
+ });
127
+ console.error("SERVER.TS: McpServer instance created.");
128
+ // --- End MCP Server Initialization ---
129
+ // --- Python Script Templates (Imported from v1.6.9) ---
130
+ const ENSURE_PROJECT_OPEN_PYTHON_SNIPPET = `
131
+ import sys
132
+ import scriptengine as script_engine
133
+ import os
134
+ import time
135
+ import traceback
136
+
137
+ # --- Function to ensure the correct project is open ---
138
+ MAX_RETRIES = 3
139
+ RETRY_DELAY = 2.0 # seconds (use float for time.sleep)
140
+
141
+ def ensure_project_open(target_project_path):
142
+ print("DEBUG: Ensuring project is open: %s" % target_project_path)
143
+ # Normalize target path once
144
+ normalized_target_path = os.path.normcase(os.path.abspath(target_project_path))
145
+
146
+ for attempt in range(MAX_RETRIES):
147
+ print("DEBUG: Ensure project attempt %d/%d for %s" % (attempt + 1, MAX_RETRIES, normalized_target_path))
148
+ primary_project = None
149
+ try:
150
+ # Getting primary project might fail if CODESYS instance is unstable
151
+ primary_project = script_engine.projects.primary
152
+ except Exception as primary_err:
153
+ print("WARN: Error getting primary project: %s. Assuming none." % primary_err)
154
+ # traceback.print_exc() # Optional: Print stack trace for this error
155
+ primary_project = None
156
+
157
+ current_project_path = ""
158
+ project_ok = False # Flag to check if target is confirmed primary and accessible
159
+
160
+ if primary_project:
161
+ try:
162
+ # Getting path should be relatively safe if primary_project object exists
163
+ current_project_path = os.path.normcase(os.path.abspath(primary_project.path))
164
+ print("DEBUG: Current primary project path: %s" % current_project_path)
165
+ if current_project_path == normalized_target_path:
166
+ # Found the right project as primary, now check if it's usable
167
+ print("DEBUG: Target project path matches primary. Checking access...")
168
+ try:
169
+ # Try a relatively safe operation to confirm object usability
170
+ # Getting children count is a reasonable check
171
+ _ = len(primary_project.get_children(False))
172
+ print("DEBUG: Target project '%s' is primary and accessible." % target_project_path)
173
+ project_ok = True
174
+ return primary_project # SUCCESS CASE 1: Already open and accessible
175
+ except Exception as access_err:
176
+ # Project found, but accessing it failed. Might be unstable.
177
+ print("WARN: Primary project access check failed for '%s': %s. Will attempt reopen." % (current_project_path, access_err))
178
+ # traceback.print_exc() # Optional: Print stack trace
179
+ primary_project = None # Force reopen by falling through
180
+ else:
181
+ # A *different* project is primary
182
+ print("DEBUG: Primary project is '%s', not the target '%s'." % (current_project_path, normalized_target_path))
183
+ # Consider closing the wrong project if causing issues, but for now, just open target
184
+ # try:
185
+ # print("DEBUG: Closing incorrect primary project '%s'..." % current_project_path)
186
+ # primary_project.close() # Be careful with unsaved changes
187
+ # except Exception as close_err:
188
+ # print("WARN: Failed to close incorrect primary project: %s" % close_err)
189
+ primary_project = None # Force open target project
190
+
191
+ except Exception as path_err:
192
+ # Failed even to get the path of the supposed primary project
193
+ print("WARN: Could not get path of current primary project: %s. Assuming not the target." % path_err)
194
+ # traceback.print_exc() # Optional: Print stack trace
195
+ primary_project = None # Force open target project
196
+
197
+ # If target project not confirmed as primary and accessible, attempt to open/reopen
198
+ if not project_ok:
199
+ # Log clearly whether we are opening initially or reopening
200
+ if primary_project is None and current_project_path == "":
201
+ print("DEBUG: No primary project detected. Attempting to open target: %s" % target_project_path)
202
+ elif primary_project is None and current_project_path != "":
203
+ print("DEBUG: Primary project was '%s' but failed access check or needed close. Attempting to open target: %s" % (current_project_path, target_project_path))
204
+ else: # Includes cases where wrong project was open
205
+ print("DEBUG: Target project not primary or initial check failed. Attempting to open/reopen: %s" % target_project_path)
206
+
207
+ try:
208
+ # Set flags for silent opening, handle potential attribute errors
209
+ update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
210
+ # try:
211
+ # update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
212
+ # except AttributeError:
213
+ # print("WARN: VersionUpdateFlags not found, using integer flags for open (1 | 2 = 3).")
214
+ # update_mode = 3 # 1=NoUpdates, 2=SilentMode
215
+
216
+ opened_project = None
217
+ try:
218
+ # The actual open call
219
+ print("DEBUG: Calling script_engine.projects.open('%s', update_flags=%s)..." % (target_project_path, update_mode))
220
+ opened_project = script_engine.projects.open(target_project_path, update_flags=update_mode)
221
+
222
+ if not opened_project:
223
+ # This is a critical failure if open returns None without exception
224
+ print("ERROR: projects.open returned None for %s on attempt %d" % (target_project_path, attempt + 1))
225
+ # Allow retry loop to continue
226
+ else:
227
+ # Open call returned *something*, let's verify
228
+ print("DEBUG: projects.open call returned an object for: %s" % target_project_path)
229
+ print("DEBUG: Pausing for stabilization after open...")
230
+ time.sleep(RETRY_DELAY) # Give CODESYS time
231
+ # Re-verify: Is the project now primary and accessible?
232
+ recheck_primary = None
233
+ try: recheck_primary = script_engine.projects.primary
234
+ except Exception as recheck_primary_err: print("WARN: Error getting primary project after reopen: %s" % recheck_primary_err)
235
+
236
+ if recheck_primary:
237
+ recheck_path = ""
238
+ try: # Getting path might fail
239
+ recheck_path = os.path.normcase(os.path.abspath(recheck_primary.path))
240
+ except Exception as recheck_path_err:
241
+ print("WARN: Failed to get path after reopen: %s" % recheck_path_err)
242
+
243
+ if recheck_path == normalized_target_path:
244
+ print("DEBUG: Target project confirmed as primary after reopening.")
245
+ try: # Final sanity check
246
+ _ = len(recheck_primary.get_children(False))
247
+ print("DEBUG: Reopened project basic access confirmed.")
248
+ return recheck_primary # SUCCESS CASE 2: Successfully opened/reopened
249
+ except Exception as access_err_reopen:
250
+ print("WARN: Reopened project (%s) basic access check failed: %s." % (normalized_target_path, access_err_reopen))
251
+ # traceback.print_exc() # Optional
252
+ # Allow retry loop to continue
253
+ else:
254
+ print("WARN: Different project is primary after reopening! Expected '%s', got '%s'." % (normalized_target_path, recheck_path))
255
+ # Allow retry loop to continue, maybe it fixes itself
256
+ else:
257
+ print("WARN: No primary project found after reopening attempt %d!" % (attempt+1))
258
+ # Allow retry loop to continue
259
+
260
+ except Exception as open_err:
261
+ # Catch errors during the open call itself
262
+ print("ERROR: Exception during projects.open call on attempt %d: %s" % (attempt + 1, open_err))
263
+ traceback.print_exc() # Crucial for diagnosing open failures
264
+ # Allow retry loop to continue
265
+
266
+ except Exception as outer_open_err:
267
+ # Catch errors in the flag setup etc.
268
+ print("ERROR: Unexpected error during open setup/logic attempt %d: %s" % (attempt + 1, outer_open_err))
269
+ traceback.print_exc()
270
+
271
+ # If we didn't return successfully in this attempt, wait before retrying
272
+ if attempt < MAX_RETRIES - 1:
273
+ print("DEBUG: Ensure project attempt %d did not succeed. Waiting %f seconds..." % (attempt + 1, RETRY_DELAY))
274
+ time.sleep(RETRY_DELAY)
275
+ else: # Last attempt failed
276
+ print("ERROR: Failed all ensure_project_open attempts for %s." % normalized_target_path)
277
+
278
+
279
+ # If all retries fail after the loop
280
+ raise RuntimeError("Failed to ensure project '%s' is open and accessible after %d attempts." % (target_project_path, MAX_RETRIES))
281
+ # --- End of function ---
282
+
283
+ # Placeholder for the project file path (must be set in scripts using this snippet)
284
+ PROJECT_FILE_PATH = r"{PROJECT_FILE_PATH}"
273
285
  `;
274
- // (CHECK_STATUS_SCRIPT remains the same)
275
- const CHECK_STATUS_SCRIPT = `
276
- import sys, scriptengine as script_engine, os, traceback
277
- project_open = False; project_name = "No project open"; project_path = "N/A"; scripting_ok = False
278
- try:
279
- scripting_ok = True; primary_project = script_engine.projects.primary
280
- if primary_project:
281
- project_open = True
282
- try:
283
- project_path = os.path.normcase(os.path.abspath(primary_project.path))
284
- try:
285
- project_name = primary_project.get_name() # Might fail
286
- if not project_name: project_name = "Unnamed (path: %s)" % os.path.basename(project_path)
287
- except: project_name = "Unnamed (path: %s)" % os.path.basename(project_path)
288
- except Exception as e_path: project_path = "N/A (Error: %s)" % e_path; project_name = "Unnamed (Path Error)"
289
- print("Project Open: %s" % project_open); print("Project Name: %s" % project_name)
290
- print("Project Path: %s" % project_path); print("Scripting OK: %s" % scripting_ok)
291
- print("SCRIPT_SUCCESS: Status check complete."); sys.exit(0)
292
- except Exception as e:
293
- error_message = "Error during status check: %s" % e
294
- print(error_message); print("Scripting OK: False")
295
- # traceback.print_exc() # Optional traceback
296
- print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
286
+ const CHECK_STATUS_SCRIPT = `
287
+ import sys, scriptengine as script_engine, os, traceback
288
+ project_open = False; project_name = "No project open"; project_path = "N/A"; scripting_ok = False
289
+ try:
290
+ scripting_ok = True; primary_project = script_engine.projects.primary
291
+ if primary_project:
292
+ project_open = True
293
+ try:
294
+ project_path = os.path.normcase(os.path.abspath(primary_project.path))
295
+ try:
296
+ project_name = primary_project.get_name() # Might fail
297
+ if not project_name: project_name = "Unnamed (path: %s)" % os.path.basename(project_path)
298
+ except: project_name = "Unnamed (path: %s)" % os.path.basename(project_path)
299
+ except Exception as e_path: project_path = "N/A (Error: %s)" % e_path; project_name = "Unnamed (Path Error)"
300
+ print("Project Open: %s" % project_open); print("Project Name: %s" % project_name)
301
+ print("Project Path: %s" % project_path); print("Scripting OK: %s" % scripting_ok)
302
+ print("SCRIPT_SUCCESS: Status check complete."); sys.exit(0)
303
+ except Exception as e:
304
+ error_message = "Error during status check: %s" % e
305
+ print(error_message); print("Scripting OK: False")
306
+ # traceback.print_exc() # Optional traceback
307
+ print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
297
308
  `;
298
- // (OPEN_PROJECT_SCRIPT_TEMPLATE remains the same)
299
- const OPEN_PROJECT_SCRIPT_TEMPLATE = `
300
- import sys, scriptengine as script_engine, os, traceback
301
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
302
- try:
303
- project = ensure_project_open(PROJECT_FILE_PATH)
304
- # Get name from object if possible, otherwise use path basename
305
- proj_name = "Unknown"
306
- try:
307
- if project: proj_name = project.get_name() or os.path.basename(PROJECT_FILE_PATH)
308
- else: proj_name = os.path.basename(PROJECT_FILE_PATH) + " (ensure_project_open returned None?)"
309
- except Exception:
310
- proj_name = os.path.basename(PROJECT_FILE_PATH) + " (name retrieval failed)"
311
- print("Project Opened: %s" % proj_name)
312
- print("SCRIPT_SUCCESS: Project opened successfully.")
313
- sys.exit(0)
314
- except Exception as e:
315
- error_message = "Error opening project %s: %s" % (PROJECT_FILE_PATH, e)
316
- print(error_message)
317
- traceback.print_exc()
318
- print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
309
+ const OPEN_PROJECT_SCRIPT_TEMPLATE = `
310
+ import sys, scriptengine as script_engine, os, traceback
311
+ ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
312
+ try:
313
+ project = ensure_project_open(PROJECT_FILE_PATH)
314
+ # Get name from object if possible, otherwise use path basename
315
+ proj_name = "Unknown"
316
+ try:
317
+ if project: proj_name = project.get_name() or os.path.basename(PROJECT_FILE_PATH)
318
+ else: proj_name = os.path.basename(PROJECT_FILE_PATH) + " (ensure_project_open returned None?)"
319
+ except Exception:
320
+ proj_name = os.path.basename(PROJECT_FILE_PATH) + " (name retrieval failed)"
321
+ print("Project Opened: %s" % proj_name)
322
+ print("SCRIPT_SUCCESS: Project opened successfully.")
323
+ sys.exit(0)
324
+ except Exception as e:
325
+ error_message = "Error opening project %s: %s" % (PROJECT_FILE_PATH, e)
326
+ print(error_message)
327
+ traceback.print_exc()
328
+ print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
319
329
  `;
320
- // (CREATE_PROJECT_SCRIPT_TEMPLATE remains the same)
321
- const CREATE_PROJECT_SCRIPT_TEMPLATE = `
322
- import sys, scriptengine as script_engine, os, shutil, time, traceback
323
- # Placeholders
324
- TEMPLATE_PROJECT_PATH = r'{TEMPLATE_PROJECT_PATH}' # Path to Standard.project
325
- PROJECT_FILE_PATH = r'{PROJECT_FILE_PATH}' # Path for the new project (Target Path)
326
- try:
327
- print("DEBUG: Python script create_project (copy from template):")
328
- print("DEBUG: Template Source = %s" % TEMPLATE_PROJECT_PATH)
329
- print("DEBUG: Target Path = %s" % PROJECT_FILE_PATH)
330
- if not PROJECT_FILE_PATH: raise ValueError("Target project file path empty.")
331
- if not TEMPLATE_PROJECT_PATH: raise ValueError("Template project file path empty.")
332
- if not os.path.exists(TEMPLATE_PROJECT_PATH): raise IOError("Template project file not found: %s" % TEMPLATE_PROJECT_PATH)
333
-
334
- # 1. Copy the template project file to the new location
335
- target_dir = os.path.dirname(PROJECT_FILE_PATH)
336
- if not os.path.exists(target_dir): print("DEBUG: Creating target directory: %s" % target_dir); os.makedirs(target_dir)
337
- # Check if target file already exists
338
- if os.path.exists(PROJECT_FILE_PATH): print("WARN: Target project file already exists, overwriting: %s" % PROJECT_FILE_PATH)
339
-
340
- print("DEBUG: Copying '%s' to '%s'..." % (TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH))
341
- shutil.copy2(TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH) # copy2 preserves metadata
342
- print("DEBUG: File copy complete.")
343
-
344
- # 2. Open the newly copied project file
345
- print("DEBUG: Opening the copied project: %s" % PROJECT_FILE_PATH)
346
- # Set flags for silent opening
347
- update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
348
- # try:
349
- # update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
350
- # except AttributeError:
351
- # print("WARN: VersionUpdateFlags not found, using integer flags for open (1 | 2 = 3).")
352
- # update_mode = 3
353
-
354
- project = script_engine.projects.open(PROJECT_FILE_PATH, update_flags=update_mode)
355
- print("DEBUG: script_engine.projects.open returned: %s" % project)
356
- if project:
357
- print("DEBUG: Pausing briefly after open...")
358
- time.sleep(1.0) # Allow CODESYS to potentially initialize things
359
- try:
360
- print("DEBUG: Explicitly saving project after opening copy...")
361
- project.save();
362
- print("DEBUG: Project save after opening copy succeeded.")
363
- except Exception as save_err:
364
- print("WARN: Explicit save after opening copy failed: %s" % save_err)
365
- # Decide if this is critical - maybe not, but good to know.
366
- print("Project Created from Template Copy at: %s" % PROJECT_FILE_PATH)
367
- print("SCRIPT_SUCCESS: Project copied from template and opened successfully.")
368
- sys.exit(0)
369
- else:
370
- error_message = "Failed to open project copy %s after copying template %s. projects.open returned None." % (PROJECT_FILE_PATH, TEMPLATE_PROJECT_PATH)
371
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
372
- except Exception as e:
373
- detailed_error = traceback.format_exc()
374
- error_message = "Error creating project '%s' from template '%s': %s\\n%s" % (PROJECT_FILE_PATH, TEMPLATE_PROJECT_PATH, e, detailed_error)
375
- print(error_message); print("SCRIPT_ERROR: Error copying/opening template: %s" % e); sys.exit(1)
330
+ const CREATE_PROJECT_SCRIPT_TEMPLATE = `
331
+ import sys, scriptengine as script_engine, os, shutil, time, traceback
332
+ # Placeholders
333
+ TEMPLATE_PROJECT_PATH = r'{TEMPLATE_PROJECT_PATH}' # Path to Standard.project
334
+ PROJECT_FILE_PATH = r'{PROJECT_FILE_PATH}' # Path for the new project (Target Path)
335
+ try:
336
+ print("DEBUG: Python script create_project (copy from template):")
337
+ print("DEBUG: Template Source = %s" % TEMPLATE_PROJECT_PATH)
338
+ print("DEBUG: Target Path = %s" % PROJECT_FILE_PATH)
339
+ if not PROJECT_FILE_PATH: raise ValueError("Target project file path empty.")
340
+ if not TEMPLATE_PROJECT_PATH: raise ValueError("Template project file path empty.")
341
+ if not os.path.exists(TEMPLATE_PROJECT_PATH): raise IOError("Template project file not found: %s" % TEMPLATE_PROJECT_PATH)
342
+
343
+ # 1. Copy the template project file to the new location
344
+ target_dir = os.path.dirname(PROJECT_FILE_PATH)
345
+ if not os.path.exists(target_dir): print("DEBUG: Creating target directory: %s" % target_dir); os.makedirs(target_dir)
346
+ # Check if target file already exists
347
+ if os.path.exists(PROJECT_FILE_PATH): print("WARN: Target project file already exists, overwriting: %s" % PROJECT_FILE_PATH)
348
+
349
+ print("DEBUG: Copying '%s' to '%s'..." % (TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH))
350
+ shutil.copy2(TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH) # copy2 preserves metadata
351
+ print("DEBUG: File copy complete.")
352
+
353
+ # 2. Open the newly copied project file
354
+ print("DEBUG: Opening the copied project: %s" % PROJECT_FILE_PATH)
355
+ # Set flags for silent opening
356
+ update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
357
+ # try:
358
+ # update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
359
+ # except AttributeError:
360
+ # print("WARN: VersionUpdateFlags not found, using integer flags for open (1 | 2 = 3).")
361
+ # update_mode = 3
362
+
363
+ project = script_engine.projects.open(PROJECT_FILE_PATH, update_flags=update_mode)
364
+ print("DEBUG: script_engine.projects.open returned: %s" % project)
365
+ if project:
366
+ print("DEBUG: Pausing briefly after open...")
367
+ time.sleep(1.0) # Allow CODESYS to potentially initialize things
368
+ try:
369
+ print("DEBUG: Explicitly saving project after opening copy...")
370
+ project.save();
371
+ print("DEBUG: Project save after opening copy succeeded.")
372
+ except Exception as save_err:
373
+ print("WARN: Explicit save after opening copy failed: %s" % save_err)
374
+ # Decide if this is critical - maybe not, but good to know.
375
+ print("Project Created from Template Copy at: %s" % PROJECT_FILE_PATH)
376
+ print("SCRIPT_SUCCESS: Project copied from template and opened successfully.")
377
+ sys.exit(0)
378
+ else:
379
+ error_message = "Failed to open project copy %s after copying template %s. projects.open returned None." % (PROJECT_FILE_PATH, TEMPLATE_PROJECT_PATH)
380
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
381
+ except Exception as e:
382
+ detailed_error = traceback.format_exc()
383
+ error_message = "Error creating project '%s' from template '%s': %s\\n%s" % (PROJECT_FILE_PATH, TEMPLATE_PROJECT_PATH, e, detailed_error)
384
+ print(error_message); print("SCRIPT_ERROR: Error copying/opening template: %s" % e); sys.exit(1)
376
385
  `;
377
- // (SAVE_PROJECT_SCRIPT_TEMPLATE remains the same)
378
- const SAVE_PROJECT_SCRIPT_TEMPLATE = `
379
- import sys, scriptengine as script_engine, os, traceback
380
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
381
- try:
382
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
383
- # Get name from object if possible, otherwise use path basename
384
- project_name = "Unknown"
385
- try:
386
- if primary_project: project_name = primary_project.get_name() or os.path.basename(PROJECT_FILE_PATH)
387
- else: project_name = os.path.basename(PROJECT_FILE_PATH) + " (ensure_project_open returned None?)"
388
- except Exception:
389
- project_name = os.path.basename(PROJECT_FILE_PATH) + " (name retrieval failed)"
390
-
391
- print("DEBUG: Saving project: %s (%s)" % (project_name, PROJECT_FILE_PATH))
392
- primary_project.save()
393
- print("DEBUG: project.save() executed.")
394
- print("Project Saved: %s" % project_name)
395
- print("SCRIPT_SUCCESS: Project saved successfully.")
396
- sys.exit(0)
397
- except Exception as e:
398
- error_message = "Error saving project %s: %s" % (PROJECT_FILE_PATH, e)
399
- print(error_message)
400
- traceback.print_exc()
401
- print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
386
+ const SAVE_PROJECT_SCRIPT_TEMPLATE = `
387
+ import sys, scriptengine as script_engine, os, traceback
388
+ ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
389
+ try:
390
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
391
+ # Get name from object if possible, otherwise use path basename
392
+ project_name = "Unknown"
393
+ try:
394
+ if primary_project: project_name = primary_project.get_name() or os.path.basename(PROJECT_FILE_PATH)
395
+ else: project_name = os.path.basename(PROJECT_FILE_PATH) + " (ensure_project_open returned None?)"
396
+ except Exception:
397
+ project_name = os.path.basename(PROJECT_FILE_PATH) + " (name retrieval failed)"
398
+
399
+ print("DEBUG: Saving project: %s (%s)" % (project_name, PROJECT_FILE_PATH))
400
+ primary_project.save()
401
+ print("DEBUG: project.save() executed.")
402
+ print("Project Saved: %s" % project_name)
403
+ print("SCRIPT_SUCCESS: Project saved successfully.")
404
+ sys.exit(0)
405
+ except Exception as e:
406
+ error_message = "Error saving project %s: %s" % (PROJECT_FILE_PATH, e)
407
+ print(error_message)
408
+ traceback.print_exc()
409
+ print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
402
410
  `;
403
- const PouTypeEnum = zod_1.z.enum(["Program", "FunctionBlock", "Function"]);
404
- const ImplementationLanguageEnum = zod_1.z.enum(["ST", "LD", "FBD", "SFC", "IL", "CFC", "StructuredText", "LadderDiagram", "FunctionBlockDiagram", "SequentialFunctionChart", "InstructionList", "ContinuousFunctionChart"]);
405
- // --- Path finding function (used by multiple templates) ---
406
- const FIND_OBJECT_BY_PATH_PYTHON_SNIPPET = `
407
- import traceback
408
- # --- Find object by path function ---
409
- def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
410
- print("DEBUG: Finding %s by path: '%s'" % (target_type_name, full_path))
411
- normalized_path = full_path.replace('\\\\', '/').strip('/')
412
- path_parts = normalized_path.split('/')
413
- if not path_parts:
414
- print("ERROR: Path is empty.")
415
- return None
416
-
417
- # Determine the actual starting node (project or application)
418
- project = start_node # Assume start_node is project initially
419
- if not hasattr(start_node, 'active_application') and hasattr(start_node, 'project'):
420
- # If start_node is not project but has project ref (e.g., an application), get the project
421
- try: project = start_node.project
422
- except Exception as proj_ref_err:
423
- print("WARN: Could not get project reference from start_node: %s" % proj_ref_err)
424
- # Proceed assuming start_node might be the project anyway or search fails
425
-
426
- # Try to get the application object robustly if we think we have the project
427
- app = None
428
- if hasattr(project, 'active_application'):
429
- try: app = project.active_application
430
- except Exception: pass # Ignore errors getting active app
431
- if not app:
432
- try:
433
- apps = project.find("Application", True) # Search recursively
434
- if apps: app = apps[0]
435
- except Exception: pass
436
-
437
- # Check if the first path part matches the application name
438
- app_name_lower = ""
439
- if app:
440
- try: app_name_lower = (app.get_name() or "application").lower()
441
- except Exception: app_name_lower = "application" # Fallback
442
-
443
- # Decide where to start the traversal
444
- current_obj = start_node # Default to the node passed in
445
- if hasattr(project, 'active_application'): # Only adjust if start_node was likely the project
446
- if app and path_parts[0].lower() == app_name_lower:
447
- print("DEBUG: Path starts with Application name '%s'. Beginning search there." % path_parts[0])
448
- current_obj = app
449
- path_parts = path_parts[1:] # Consume the app name part
450
- # If path was *only* the application name
451
- if not path_parts:
452
- print("DEBUG: Target path is the Application object itself.")
453
- return current_obj
454
- else:
455
- print("DEBUG: Path does not start with Application name. Starting search from project root.")
456
- current_obj = project # Start search from the project root
457
- else:
458
- print("DEBUG: Starting search from originally provided node.")
459
-
460
-
461
- # Traverse the remaining path parts
462
- parent_path_str = getattr(current_obj, 'get_name', lambda: str(current_obj))() # Safer name getting
463
-
464
- for i, part_name in enumerate(path_parts):
465
- is_last_part = (i == len(path_parts) - 1)
466
- print("DEBUG: Searching for part [%d/%d]: '%s' under '%s'" % (i+1, len(path_parts), part_name, parent_path_str))
467
- found_in_parent = None
468
- try:
469
- # Prioritize non-recursive find for direct children
470
- children_of_current = current_obj.get_children(False)
471
- print("DEBUG: Found %d direct children under '%s'." % (len(children_of_current), parent_path_str))
472
- for child in children_of_current:
473
- child_name = getattr(child, 'get_name', lambda: None)() # Safer name getting
474
- # print("DEBUG: Checking child: '%s'" % child_name) # Verbose
475
- if child_name == part_name:
476
- found_in_parent = child
477
- print("DEBUG: Found direct child matching '%s'." % part_name)
478
- break # Found direct child, stop searching children
479
-
480
- # If not found directly, AND it's the last part, try recursive find from current parent
481
- if not found_in_parent and is_last_part:
482
- print("DEBUG: Direct find failed for last part '%s'. Trying recursive find under '%s'." % (part_name, parent_path_str))
483
- found_recursive_list = current_obj.find(part_name, True) # Recursive find
484
- if found_recursive_list:
485
- # Maybe add a check here if multiple are found?
486
- found_in_parent = found_recursive_list[0] # Take the first match
487
- print("DEBUG: Found last part '%s' recursively." % part_name)
488
- else:
489
- print("DEBUG: Recursive find also failed for last part '%s'." % part_name)
490
-
491
- # Update current object if found
492
- if found_in_parent:
493
- current_obj = found_in_parent
494
- parent_path_str = getattr(current_obj, 'get_name', lambda: part_name)() # Safer name getting
495
- print("DEBUG: Stepped into '%s'." % parent_path_str)
496
- else:
497
- # If not found at any point, the path is invalid from this parent
498
- print("ERROR: Path part '%s' not found under '%s'." % (part_name, parent_path_str))
499
- return None # Path broken
500
-
501
- except Exception as find_err:
502
- print("ERROR: Exception while searching for '%s' under '%s': %s" % (part_name, parent_path_str, find_err))
503
- traceback.print_exc()
504
- return None # Error during search
505
-
506
- # Final verification (optional but recommended): Check if the found object's name matches the last part
507
- final_expected_name = full_path.split('/')[-1]
508
- found_final_name = getattr(current_obj, 'get_name', lambda: None)() # Safer name getting
509
-
510
- if found_final_name == final_expected_name:
511
- print("DEBUG: Final %s found and name verified for path '%s': %s" % (target_type_name, full_path, found_final_name))
512
- return current_obj
513
- else:
514
- print("ERROR: Traversal ended on object '%s' but expected final name was '%s'." % (found_final_name, final_expected_name))
515
- return None # Name mismatch implies target not found as expected
516
-
517
- # --- End of find object function ---
411
+ const FIND_OBJECT_BY_PATH_PYTHON_SNIPPET = `
412
+ import traceback
413
+ # --- Find object by path function ---
414
+ def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
415
+ print("DEBUG: Finding %s by path: '%s'" % (target_type_name, full_path))
416
+ normalized_path = full_path.replace('\\\\', '/').strip('/')
417
+ path_parts = normalized_path.split('/')
418
+ if not path_parts:
419
+ print("ERROR: Path is empty.")
420
+ return None
421
+
422
+ # Determine the actual starting node (project or application)
423
+ project = start_node # Assume start_node is project initially
424
+ if not hasattr(start_node, 'active_application') and hasattr(start_node, 'project'):
425
+ # If start_node is not project but has project ref (e.g., an application), get the project
426
+ try: project = start_node.project
427
+ except Exception as proj_ref_err:
428
+ print("WARN: Could not get project reference from start_node: %s" % proj_ref_err)
429
+ # Proceed assuming start_node might be the project anyway or search fails
430
+
431
+ # Try to get the application object robustly if we think we have the project
432
+ app = None
433
+ if hasattr(project, 'active_application'):
434
+ try: app = project.active_application
435
+ except Exception: pass # Ignore errors getting active app
436
+ if not app:
437
+ try:
438
+ apps = project.find("Application", True) # Search recursively
439
+ if apps: app = apps[0]
440
+ except Exception: pass
441
+
442
+ # Check if the first path part matches the application name
443
+ app_name_lower = ""
444
+ if app:
445
+ try: app_name_lower = (app.get_name() or "application").lower()
446
+ except Exception: app_name_lower = "application" # Fallback
447
+
448
+ # Decide where to start the traversal
449
+ current_obj = start_node # Default to the node passed in
450
+ if hasattr(project, 'active_application'): # Only adjust if start_node was likely the project
451
+ if app and path_parts[0].lower() == app_name_lower:
452
+ print("DEBUG: Path starts with Application name '%s'. Beginning search there." % path_parts[0])
453
+ current_obj = app
454
+ path_parts = path_parts[1:] # Consume the app name part
455
+ # If path was *only* the application name
456
+ if not path_parts:
457
+ print("DEBUG: Target path is the Application object itself.")
458
+ return current_obj
459
+ else:
460
+ print("DEBUG: Path does not start with Application name. Starting search from project root.")
461
+ current_obj = project # Start search from the project root
462
+ else:
463
+ print("DEBUG: Starting search from originally provided node.")
464
+
465
+
466
+ # Traverse the remaining path parts
467
+ parent_path_str = getattr(current_obj, 'get_name', lambda: str(current_obj))() # Safer name getting
468
+
469
+ for i, part_name in enumerate(path_parts):
470
+ is_last_part = (i == len(path_parts) - 1)
471
+ print("DEBUG: Searching for part [%d/%d]: '%s' under '%s'" % (i+1, len(path_parts), part_name, parent_path_str))
472
+ found_in_parent = None
473
+ try:
474
+ # Prioritize non-recursive find for direct children
475
+ children_of_current = current_obj.get_children(False)
476
+ print("DEBUG: Found %d direct children under '%s'." % (len(children_of_current), parent_path_str))
477
+ for child in children_of_current:
478
+ child_name = getattr(child, 'get_name', lambda: None)() # Safer name getting
479
+ # print("DEBUG: Checking child: '%s'" % child_name) # Verbose
480
+ if child_name == part_name:
481
+ found_in_parent = child
482
+ print("DEBUG: Found direct child matching '%s'." % part_name)
483
+ break # Found direct child, stop searching children
484
+
485
+ # If not found directly, AND it's the last part, try recursive find from current parent
486
+ if not found_in_parent and is_last_part:
487
+ print("DEBUG: Direct find failed for last part '%s'. Trying recursive find under '%s'." % (part_name, parent_path_str))
488
+ found_recursive_list = current_obj.find(part_name, True) # Recursive find
489
+ if found_recursive_list:
490
+ # Maybe add a check here if multiple are found?
491
+ found_in_parent = found_recursive_list[0] # Take the first match
492
+ print("DEBUG: Found last part '%s' recursively." % part_name)
493
+ else:
494
+ print("DEBUG: Recursive find also failed for last part '%s'." % part_name)
495
+
496
+ # Update current object if found
497
+ if found_in_parent:
498
+ current_obj = found_in_parent
499
+ parent_path_str = getattr(current_obj, 'get_name', lambda: part_name)() # Safer name getting
500
+ print("DEBUG: Stepped into '%s'." % parent_path_str)
501
+ else:
502
+ # If not found at any point, the path is invalid from this parent
503
+ print("ERROR: Path part '%s' not found under '%s'." % (part_name, parent_path_str))
504
+ return None # Path broken
505
+
506
+ except Exception as find_err:
507
+ print("ERROR: Exception while searching for '%s' under '%s': %s" % (part_name, parent_path_str, find_err))
508
+ traceback.print_exc()
509
+ return None # Error during search
510
+
511
+ # Final verification (optional but recommended): Check if the found object's name matches the last part
512
+ final_expected_name = full_path.split('/')[-1]
513
+ found_final_name = getattr(current_obj, 'get_name', lambda: None)() # Safer name getting
514
+
515
+ if found_final_name == final_expected_name:
516
+ print("DEBUG: Final %s found and name verified for path '%s': %s" % (target_type_name, full_path, found_final_name))
517
+ return current_obj
518
+ else:
519
+ print("ERROR: Traversal ended on object '%s' but expected final name was '%s'." % (found_final_name, final_expected_name))
520
+ return None # Name mismatch implies target not found as expected
521
+
522
+ # --- End of find object function ---
518
523
  `;
519
- // (CREATE_POU_SCRIPT_TEMPLATE uses FIND_OBJECT_BY_PATH_PYTHON_SNIPPET)
520
- const CREATE_POU_SCRIPT_TEMPLATE = `
521
- import sys, scriptengine as script_engine, os, traceback
522
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
523
- ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
524
- POU_NAME = "{POU_NAME}"; POU_TYPE_STR = "{POU_TYPE_STR}"; IMPL_LANGUAGE_STR = "{IMPL_LANGUAGE_STR}"; PARENT_PATH_REL = "{PARENT_PATH}"
525
- pou_type_map = { "Program": script_engine.PouType.Program, "FunctionBlock": script_engine.PouType.FunctionBlock, "Function": script_engine.PouType.Function }
526
- # Map common language names to ImplementationLanguages attributes if needed (optional, None usually works)
527
- # lang_map = { "ST": script_engine.ImplementationLanguage.st, ... }
528
-
529
- try:
530
- print("DEBUG: create_pou script: Name='%s', Type='%s', Lang='%s', ParentPath='%s', Project='%s'" % (POU_NAME, POU_TYPE_STR, IMPL_LANGUAGE_STR, PARENT_PATH_REL, PROJECT_FILE_PATH))
531
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
532
- if not POU_NAME: raise ValueError("POU name empty.")
533
- if not PARENT_PATH_REL: raise ValueError("Parent path empty.")
534
-
535
- # Resolve POU Type Enum
536
- pou_type_enum = pou_type_map.get(POU_TYPE_STR)
537
- if not pou_type_enum: raise ValueError("Invalid POU type string: %s. Use Program, FunctionBlock, or Function." % POU_TYPE_STR)
538
-
539
- # Find parent object using the robust function
540
- parent_object = find_object_by_path_robust(primary_project, PARENT_PATH_REL, "parent container")
541
- if not parent_object: raise ValueError("Parent object not found for path: %s" % PARENT_PATH_REL)
542
-
543
- parent_name = getattr(parent_object, 'get_name', lambda: str(parent_object))()
544
- print("DEBUG: Using parent object: %s (Type: %s)" % (parent_name, type(parent_object).__name__))
545
-
546
- # Check if parent object supports creating POUs (should implement ScriptIecLanguageObjectContainer)
547
- if not hasattr(parent_object, 'create_pou'):
548
- raise TypeError("Parent object '%s' of type %s does not support create_pou." % (parent_name, type(parent_object).__name__))
549
-
550
- # Set language GUID to None (let CODESYS default based on parent/settings)
551
- lang_guid = None
552
- print("DEBUG: Setting language to None (will use default).")
553
- # Example if mapping language string: lang_guid = lang_map.get(IMPL_LANGUAGE_STR, None)
554
-
555
- print("DEBUG: Calling parent_object.create_pou: Name='%s', Type=%s, Lang=%s" % (POU_NAME, pou_type_enum, lang_guid))
556
-
557
- # Call create_pou using keyword arguments
558
- new_pou = parent_object.create_pou(
559
- name=POU_NAME,
560
- type=pou_type_enum,
561
- language=lang_guid # Pass None
562
- )
563
-
564
- print("DEBUG: parent_object.create_pou returned: %s" % new_pou)
565
- if new_pou:
566
- new_pou_name = getattr(new_pou, 'get_name', lambda: POU_NAME)()
567
- print("DEBUG: POU object created: %s" % new_pou_name)
568
-
569
- # --- SAVE THE PROJECT TO PERSIST THE NEW POU ---
570
- try:
571
- print("DEBUG: Saving Project...")
572
- primary_project.save() # Save the overall project file
573
- print("DEBUG: Project saved successfully after POU creation.")
574
- except Exception as save_err:
575
- print("ERROR: Failed to save Project after POU creation: %s" % save_err)
576
- detailed_error = traceback.format_exc()
577
- error_message = "Error saving Project after creating POU '%s': %s\\n%s" % (new_pou_name, save_err, detailed_error)
578
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
579
- # --- END SAVING ---
580
-
581
- print("POU Created: %s" % new_pou_name); print("Type: %s" % POU_TYPE_STR); print("Language: %s (Defaulted)" % IMPL_LANGUAGE_STR); print("Parent Path: %s" % PARENT_PATH_REL)
582
- print("SCRIPT_SUCCESS: POU created successfully."); sys.exit(0)
583
- else:
584
- error_message = "Failed to create POU '%s'. create_pou returned None." % POU_NAME; print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
585
- except Exception as e:
586
- detailed_error = traceback.format_exc()
587
- error_message = "Error creating POU '%s' in project '%s': %s\\n%s" % (POU_NAME, PROJECT_FILE_PATH, e, detailed_error)
588
- print(error_message); print("SCRIPT_ERROR: Error creating POU '%s': %s" % (POU_NAME, e)); sys.exit(1)
524
+ const CREATE_POU_SCRIPT_TEMPLATE = `
525
+ import sys, scriptengine as script_engine, os, traceback
526
+ ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
527
+ ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
528
+ POU_NAME = "{POU_NAME}"; POU_TYPE_STR = "{POU_TYPE_STR}"; IMPL_LANGUAGE_STR = "{IMPL_LANGUAGE_STR}"; PARENT_PATH_REL = "{PARENT_PATH}"
529
+ pou_type_map = { "Program": script_engine.PouType.Program, "FunctionBlock": script_engine.PouType.FunctionBlock, "Function": script_engine.PouType.Function }
530
+ # Map common language names to ImplementationLanguages attributes if needed (optional, None usually works)
531
+ # lang_map = { "ST": script_engine.ImplementationLanguage.st, ... }
532
+
533
+ try:
534
+ print("DEBUG: create_pou script: Name='%s', Type='%s', Lang='%s', ParentPath='%s', Project='%s'" % (POU_NAME, POU_TYPE_STR, IMPL_LANGUAGE_STR, PARENT_PATH_REL, PROJECT_FILE_PATH))
535
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
536
+ if not POU_NAME: raise ValueError("POU name empty.")
537
+ if not PARENT_PATH_REL: raise ValueError("Parent path empty.")
538
+
539
+ # Resolve POU Type Enum
540
+ pou_type_enum = pou_type_map.get(POU_TYPE_STR)
541
+ if not pou_type_enum: raise ValueError("Invalid POU type string: %s. Use Program, FunctionBlock, or Function." % POU_TYPE_STR)
542
+
543
+ # Find parent object using the robust function
544
+ parent_object = find_object_by_path_robust(primary_project, PARENT_PATH_REL, "parent container")
545
+ if not parent_object: raise ValueError("Parent object not found for path: %s" % PARENT_PATH_REL)
546
+
547
+ parent_name = getattr(parent_object, 'get_name', lambda: str(parent_object))()
548
+ print("DEBUG: Using parent object: %s (Type: %s)" % (parent_name, type(parent_object).__name__))
549
+
550
+ # Check if parent object supports creating POUs (should implement ScriptIecLanguageObjectContainer)
551
+ if not hasattr(parent_object, 'create_pou'):
552
+ raise TypeError("Parent object '%s' of type %s does not support create_pou." % (parent_name, type(parent_object).__name__))
553
+
554
+ # Set language GUID to None (let CODESYS default based on parent/settings)
555
+ lang_guid = None
556
+ print("DEBUG: Setting language to None (will use default).")
557
+ # Example if mapping language string: lang_guid = lang_map.get(IMPL_LANGUAGE_STR, None)
558
+
559
+ print("DEBUG: Calling parent_object.create_pou: Name='%s', Type=%s, Lang=%s" % (POU_NAME, pou_type_enum, lang_guid))
560
+
561
+ # Call create_pou using keyword arguments
562
+ new_pou = parent_object.create_pou(
563
+ name=POU_NAME,
564
+ type=pou_type_enum,
565
+ language=lang_guid # Pass None
566
+ )
567
+
568
+ print("DEBUG: parent_object.create_pou returned: %s" % new_pou)
569
+ if new_pou:
570
+ new_pou_name = getattr(new_pou, 'get_name', lambda: POU_NAME)()
571
+ print("DEBUG: POU object created: %s" % new_pou_name)
572
+
573
+ # --- SAVE THE PROJECT TO PERSIST THE NEW POU ---
574
+ try:
575
+ print("DEBUG: Saving Project...")
576
+ primary_project.save() # Save the overall project file
577
+ print("DEBUG: Project saved successfully after POU creation.")
578
+ except Exception as save_err:
579
+ print("ERROR: Failed to save Project after POU creation: %s" % save_err)
580
+ detailed_error = traceback.format_exc()
581
+ error_message = "Error saving Project after creating POU '%s': %s\\n%s" % (new_pou_name, save_err, detailed_error)
582
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
583
+ # --- END SAVING ---
584
+
585
+ print("POU Created: %s" % new_pou_name); print("Type: %s" % POU_TYPE_STR); print("Language: %s (Defaulted)" % IMPL_LANGUAGE_STR); print("Parent Path: %s" % PARENT_PATH_REL)
586
+ print("SCRIPT_SUCCESS: POU created successfully."); sys.exit(0)
587
+ else:
588
+ error_message = "Failed to create POU '%s'. create_pou returned None." % POU_NAME; print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
589
+ except Exception as e:
590
+ detailed_error = traceback.format_exc()
591
+ error_message = "Error creating POU '%s' in project '%s': %s\\n%s" % (POU_NAME, PROJECT_FILE_PATH, e, detailed_error)
592
+ print(error_message); print("SCRIPT_ERROR: Error creating POU '%s': %s" % (POU_NAME, e)); sys.exit(1)
589
593
  `;
590
- // (SET_POU_CODE_SCRIPT_TEMPLATE uses FIND_OBJECT_BY_PATH_PYTHON_SNIPPET)
591
- const SET_POU_CODE_SCRIPT_TEMPLATE = `
592
- import sys, scriptengine as script_engine, os, traceback
593
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
594
- ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
595
- POU_FULL_PATH = "{POU_FULL_PATH}" # Expecting format like "Application/MyPOU" or "Folder/SubFolder/MyPOU"
596
- DECLARATION_CONTENT = """{DECLARATION_CONTENT}"""
597
- IMPLEMENTATION_CONTENT = """{IMPLEMENTATION_CONTENT}"""
598
-
599
- try:
600
- print("DEBUG: set_pou_code script: POU_FULL_PATH='%s', Project='%s'" % (POU_FULL_PATH, PROJECT_FILE_PATH))
601
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
602
- if not POU_FULL_PATH: raise ValueError("POU full path empty.")
603
-
604
- # Find the target POU/Method/Property object
605
- target_object = find_object_by_path_robust(primary_project, POU_FULL_PATH, "target object")
606
- if not target_object: raise ValueError("Target object not found using path: %s" % POU_FULL_PATH)
607
-
608
- target_name = getattr(target_object, 'get_name', lambda: POU_FULL_PATH)()
609
- print("DEBUG: Found target object: %s" % target_name)
610
-
611
- # --- Set Declaration Part ---
612
- declaration_updated = False
613
- # Check if the content is actually provided (might be None/empty if only impl is set)
614
- has_declaration_content = 'DECLARATION_CONTENT' in locals() or 'DECLARATION_CONTENT' in globals()
615
- if has_declaration_content and DECLARATION_CONTENT is not None: # Check not None
616
- if hasattr(target_object, 'textual_declaration'):
617
- decl_obj = target_object.textual_declaration
618
- if decl_obj and hasattr(decl_obj, 'replace'):
619
- try:
620
- print("DEBUG: Accessing textual_declaration...")
621
- decl_obj.replace(DECLARATION_CONTENT)
622
- print("DEBUG: Set declaration text using replace().")
623
- declaration_updated = True
624
- except Exception as decl_err:
625
- print("ERROR: Failed to set declaration text: %s" % decl_err)
626
- traceback.print_exc() # Print stack trace for detailed error
627
- else:
628
- print("WARN: Target '%s' textual_declaration attribute is None or does not have replace(). Skipping declaration update." % target_name)
629
- else:
630
- print("WARN: Target '%s' does not have textual_declaration attribute. Skipping declaration update." % target_name)
631
- else:
632
- print("DEBUG: Declaration content not provided or is None. Skipping declaration update.")
633
-
634
-
635
- # --- Set Implementation Part ---
636
- implementation_updated = False
637
- has_implementation_content = 'IMPLEMENTATION_CONTENT' in locals() or 'IMPLEMENTATION_CONTENT' in globals()
638
- if has_implementation_content and IMPLEMENTATION_CONTENT is not None: # Check not None
639
- if hasattr(target_object, 'textual_implementation'):
640
- impl_obj = target_object.textual_implementation
641
- if impl_obj and hasattr(impl_obj, 'replace'):
642
- try:
643
- print("DEBUG: Accessing textual_implementation...")
644
- impl_obj.replace(IMPLEMENTATION_CONTENT)
645
- print("DEBUG: Set implementation text using replace().")
646
- implementation_updated = True
647
- except Exception as impl_err:
648
- print("ERROR: Failed to set implementation text: %s" % impl_err)
649
- traceback.print_exc() # Print stack trace for detailed error
650
- else:
651
- print("WARN: Target '%s' textual_implementation attribute is None or does not have replace(). Skipping implementation update." % target_name)
652
- else:
653
- print("WARN: Target '%s' does not have textual_implementation attribute. Skipping implementation update." % target_name)
654
- else:
655
- print("DEBUG: Implementation content not provided or is None. Skipping implementation update.")
656
-
657
-
658
- # --- SAVE THE PROJECT TO PERSIST THE CODE CHANGE ---
659
- # Only save if something was actually updated to avoid unnecessary saves
660
- if declaration_updated or implementation_updated:
661
- try:
662
- print("DEBUG: Saving Project (after code change)...")
663
- primary_project.save() # Save the overall project file
664
- print("DEBUG: Project saved successfully after code change.")
665
- except Exception as save_err:
666
- print("ERROR: Failed to save Project after setting code: %s" % save_err)
667
- detailed_error = traceback.format_exc()
668
- error_message = "Error saving Project after code change for '%s': %s\\n%s" % (target_name, save_err, detailed_error)
669
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
670
- else:
671
- print("DEBUG: No code parts were updated, skipping project save.")
672
- # --- END SAVING ---
673
-
674
- print("Code Set For: %s" % target_name)
675
- print("Path: %s" % POU_FULL_PATH)
676
- print("SCRIPT_SUCCESS: Declaration and/or implementation set successfully."); sys.exit(0)
677
-
678
- except Exception as e:
679
- detailed_error = traceback.format_exc()
680
- error_message = "Error setting code for object '%s' in project '%s': %s\\n%s" % (POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
681
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
594
+ const SET_POU_CODE_SCRIPT_TEMPLATE = `
595
+ import sys, scriptengine as script_engine, os, traceback
596
+ ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
597
+ ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
598
+ POU_FULL_PATH = "{POU_FULL_PATH}" # Expecting format like "Application/MyPOU" or "Folder/SubFolder/MyPOU"
599
+ DECLARATION_CONTENT = """{DECLARATION_CONTENT}"""
600
+ IMPLEMENTATION_CONTENT = """{IMPLEMENTATION_CONTENT}"""
601
+
602
+ try:
603
+ print("DEBUG: set_pou_code script: POU_FULL_PATH='%s', Project='%s'" % (POU_FULL_PATH, PROJECT_FILE_PATH))
604
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
605
+ if not POU_FULL_PATH: raise ValueError("POU full path empty.")
606
+
607
+ # Find the target POU/Method/Property object
608
+ target_object = find_object_by_path_robust(primary_project, POU_FULL_PATH, "target object")
609
+ if not target_object: raise ValueError("Target object not found using path: %s" % POU_FULL_PATH)
610
+
611
+ target_name = getattr(target_object, 'get_name', lambda: POU_FULL_PATH)()
612
+ print("DEBUG: Found target object: %s" % target_name)
613
+
614
+ # --- Set Declaration Part ---
615
+ declaration_updated = False
616
+ # Check if the content is actually provided (might be None/empty if only impl is set)
617
+ has_declaration_content = 'DECLARATION_CONTENT' in locals() or 'DECLARATION_CONTENT' in globals()
618
+ if has_declaration_content and DECLARATION_CONTENT is not None: # Check not None
619
+ if hasattr(target_object, 'textual_declaration'):
620
+ decl_obj = target_object.textual_declaration
621
+ if decl_obj and hasattr(decl_obj, 'replace'):
622
+ try:
623
+ print("DEBUG: Accessing textual_declaration...")
624
+ decl_obj.replace(DECLARATION_CONTENT)
625
+ print("DEBUG: Set declaration text using replace().")
626
+ declaration_updated = True
627
+ except Exception as decl_err:
628
+ print("ERROR: Failed to set declaration text: %s" % decl_err)
629
+ traceback.print_exc() # Print stack trace for detailed error
630
+ else:
631
+ print("WARN: Target '%s' textual_declaration attribute is None or does not have replace(). Skipping declaration update." % target_name)
632
+ else:
633
+ print("WARN: Target '%s' does not have textual_declaration attribute. Skipping declaration update." % target_name)
634
+ else:
635
+ print("DEBUG: Declaration content not provided or is None. Skipping declaration update.")
636
+
637
+
638
+ # --- Set Implementation Part ---
639
+ implementation_updated = False
640
+ has_implementation_content = 'IMPLEMENTATION_CONTENT' in locals() or 'IMPLEMENTATION_CONTENT' in globals()
641
+ if has_implementation_content and IMPLEMENTATION_CONTENT is not None: # Check not None
642
+ if hasattr(target_object, 'textual_implementation'):
643
+ impl_obj = target_object.textual_implementation
644
+ if impl_obj and hasattr(impl_obj, 'replace'):
645
+ try:
646
+ print("DEBUG: Accessing textual_implementation...")
647
+ impl_obj.replace(IMPLEMENTATION_CONTENT)
648
+ print("DEBUG: Set implementation text using replace().")
649
+ implementation_updated = True
650
+ except Exception as impl_err:
651
+ print("ERROR: Failed to set implementation text: %s" % impl_err)
652
+ traceback.print_exc() # Print stack trace for detailed error
653
+ else:
654
+ print("WARN: Target '%s' textual_implementation attribute is None or does not have replace(). Skipping implementation update." % target_name)
655
+ else:
656
+ print("WARN: Target '%s' does not have textual_implementation attribute. Skipping implementation update." % target_name)
657
+ else:
658
+ print("DEBUG: Implementation content not provided or is None. Skipping implementation update.")
659
+
660
+
661
+ # --- SAVE THE PROJECT TO PERSIST THE CODE CHANGE ---
662
+ # Only save if something was actually updated to avoid unnecessary saves
663
+ if declaration_updated or implementation_updated:
664
+ try:
665
+ print("DEBUG: Saving Project (after code change)...")
666
+ primary_project.save() # Save the overall project file
667
+ print("DEBUG: Project saved successfully after code change.")
668
+ except Exception as save_err:
669
+ print("ERROR: Failed to save Project after setting code: %s" % save_err)
670
+ detailed_error = traceback.format_exc()
671
+ error_message = "Error saving Project after code change for '%s': %s\\n%s" % (target_name, save_err, detailed_error)
672
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
673
+ else:
674
+ print("DEBUG: No code parts were updated, skipping project save.")
675
+ # --- END SAVING ---
676
+
677
+ print("Code Set For: %s" % target_name)
678
+ print("Path: %s" % POU_FULL_PATH)
679
+ print("SCRIPT_SUCCESS: Declaration and/or implementation set successfully."); sys.exit(0)
680
+
681
+ except Exception as e:
682
+ detailed_error = traceback.format_exc()
683
+ error_message = "Error setting code for object '%s' in project '%s': %s\\n%s" % (POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
684
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
682
685
  `;
683
- // --- CREATE_PROPERTY_SCRIPT_TEMPLATE uses FIND_OBJECT_BY_PATH_PYTHON_SNIPPET ---
684
- const CREATE_PROPERTY_SCRIPT_TEMPLATE = `
685
- import sys, scriptengine as script_engine, os, traceback
686
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
687
- ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
688
- PARENT_POU_FULL_PATH = "{PARENT_POU_FULL_PATH}" # e.g., "Application/MyFB"
689
- PROPERTY_NAME = "{PROPERTY_NAME}"
690
- PROPERTY_TYPE = "{PROPERTY_TYPE}"
691
- # Optional: Language for Getter/Setter (usually defaults to ST)
692
- # LANG_GUID_STR = "{LANG_GUID_STR}" # Example if needed
693
-
694
- try:
695
- print("DEBUG: create_property script: ParentPOU='%s', Name='%s', Type='%s', Project='%s'" % (PARENT_POU_FULL_PATH, PROPERTY_NAME, PROPERTY_TYPE, PROJECT_FILE_PATH))
696
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
697
- if not PARENT_POU_FULL_PATH: raise ValueError("Parent POU full path empty.")
698
- if not PROPERTY_NAME: raise ValueError("Property name empty.")
699
- if not PROPERTY_TYPE: raise ValueError("Property type empty.")
700
-
701
- # Find the parent POU object
702
- parent_pou_object = find_object_by_path_robust(primary_project, PARENT_POU_FULL_PATH, "parent POU")
703
- if not parent_pou_object: raise ValueError("Parent POU object not found: %s" % PARENT_POU_FULL_PATH)
704
-
705
- parent_pou_name = getattr(parent_pou_object, 'get_name', lambda: PARENT_POU_FULL_PATH)()
706
- print("DEBUG: Found Parent POU object: %s" % parent_pou_name)
707
-
708
- # Check if parent object supports creating properties (should implement ScriptIecLanguageMemberContainer)
709
- if not hasattr(parent_pou_object, 'create_property'):
710
- raise TypeError("Parent object '%s' of type %s does not support create_property." % (parent_pou_name, type(parent_pou_object).__name__))
711
-
712
- # Default language to None (usually ST)
713
- lang_guid = None
714
- print("DEBUG: Calling create_property: Name='%s', Type='%s', Lang=%s" % (PROPERTY_NAME, PROPERTY_TYPE, lang_guid))
715
-
716
- # Call the create_property method ON THE PARENT POU
717
- new_property_object = parent_pou_object.create_property(
718
- name=PROPERTY_NAME,
719
- return_type=PROPERTY_TYPE,
720
- language=lang_guid # Pass None to use default
721
- )
722
-
723
- if new_property_object:
724
- new_prop_name = getattr(new_property_object, 'get_name', lambda: PROPERTY_NAME)()
725
- print("DEBUG: Property object created: %s" % new_prop_name)
726
-
727
- # --- SAVE THE PROJECT TO PERSIST THE NEW PROPERTY OBJECT ---
728
- try:
729
- print("DEBUG: Saving Project (after property creation)...")
730
- primary_project.save()
731
- print("DEBUG: Project saved successfully after property creation.")
732
- except Exception as save_err:
733
- print("ERROR: Failed to save Project after creating property: %s" % save_err)
734
- detailed_error = traceback.format_exc()
735
- error_message = "Error saving Project after creating property '%s': %s\\n%s" % (PROPERTY_NAME, save_err, detailed_error)
736
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
737
- # --- END SAVING ---
738
-
739
- print("Property Created: %s" % new_prop_name)
740
- print("Parent POU: %s" % PARENT_POU_FULL_PATH)
741
- print("Type: %s" % PROPERTY_TYPE)
742
- print("SCRIPT_SUCCESS: Property created successfully."); sys.exit(0)
743
- else:
744
- error_message = "Failed to create property '%s' under '%s'. create_property returned None." % (PROPERTY_NAME, parent_pou_name)
745
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
746
-
747
- except Exception as e:
748
- detailed_error = traceback.format_exc()
749
- error_message = "Error creating property '%s' under POU '%s' in project '%s': %s\\n%s" % (PROPERTY_NAME, PARENT_POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
750
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
686
+ const CREATE_PROPERTY_SCRIPT_TEMPLATE = `
687
+ import sys, scriptengine as script_engine, os, traceback
688
+ ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
689
+ ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
690
+ PARENT_POU_FULL_PATH = "{PARENT_POU_FULL_PATH}" # e.g., "Application/MyFB"
691
+ PROPERTY_NAME = "{PROPERTY_NAME}"
692
+ PROPERTY_TYPE = "{PROPERTY_TYPE}"
693
+ # Optional: Language for Getter/Setter (usually defaults to ST)
694
+ # LANG_GUID_STR = "{LANG_GUID_STR}" # Example if needed
695
+
696
+ try:
697
+ print("DEBUG: create_property script: ParentPOU='%s', Name='%s', Type='%s', Project='%s'" % (PARENT_POU_FULL_PATH, PROPERTY_NAME, PROPERTY_TYPE, PROJECT_FILE_PATH))
698
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
699
+ if not PARENT_POU_FULL_PATH: raise ValueError("Parent POU full path empty.")
700
+ if not PROPERTY_NAME: raise ValueError("Property name empty.")
701
+ if not PROPERTY_TYPE: raise ValueError("Property type empty.")
702
+
703
+ # Find the parent POU object
704
+ parent_pou_object = find_object_by_path_robust(primary_project, PARENT_POU_FULL_PATH, "parent POU")
705
+ if not parent_pou_object: raise ValueError("Parent POU object not found: %s" % PARENT_POU_FULL_PATH)
706
+
707
+ parent_pou_name = getattr(parent_pou_object, 'get_name', lambda: PARENT_POU_FULL_PATH)()
708
+ print("DEBUG: Found Parent POU object: %s" % parent_pou_name)
709
+
710
+ # Check if parent object supports creating properties (should implement ScriptIecLanguageMemberContainer)
711
+ if not hasattr(parent_pou_object, 'create_property'):
712
+ raise TypeError("Parent object '%s' of type %s does not support create_property." % (parent_pou_name, type(parent_pou_object).__name__))
713
+
714
+ # Default language to None (usually ST)
715
+ lang_guid = None
716
+ print("DEBUG: Calling create_property: Name='%s', Type='%s', Lang=%s" % (PROPERTY_NAME, PROPERTY_TYPE, lang_guid))
717
+
718
+ # Call the create_property method ON THE PARENT POU
719
+ new_property_object = parent_pou_object.create_property(
720
+ name=PROPERTY_NAME,
721
+ return_type=PROPERTY_TYPE,
722
+ language=lang_guid # Pass None to use default
723
+ )
724
+
725
+ if new_property_object:
726
+ new_prop_name = getattr(new_property_object, 'get_name', lambda: PROPERTY_NAME)()
727
+ print("DEBUG: Property object created: %s" % new_prop_name)
728
+
729
+ # --- SAVE THE PROJECT TO PERSIST THE NEW PROPERTY OBJECT ---
730
+ try:
731
+ print("DEBUG: Saving Project (after property creation)...")
732
+ primary_project.save()
733
+ print("DEBUG: Project saved successfully after property creation.")
734
+ except Exception as save_err:
735
+ print("ERROR: Failed to save Project after creating property: %s" % save_err)
736
+ detailed_error = traceback.format_exc()
737
+ error_message = "Error saving Project after creating property '%s': %s\\n%s" % (PROPERTY_NAME, save_err, detailed_error)
738
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
739
+ # --- END SAVING ---
740
+
741
+ print("Property Created: %s" % new_prop_name)
742
+ print("Parent POU: %s" % PARENT_POU_FULL_PATH)
743
+ print("Type: %s" % PROPERTY_TYPE)
744
+ print("SCRIPT_SUCCESS: Property created successfully."); sys.exit(0)
745
+ else:
746
+ error_message = "Failed to create property '%s' under '%s'. create_property returned None." % (PROPERTY_NAME, parent_pou_name)
747
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
748
+
749
+ except Exception as e:
750
+ detailed_error = traceback.format_exc()
751
+ error_message = "Error creating property '%s' under POU '%s' in project '%s': %s\\n%s" % (PROPERTY_NAME, PARENT_POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
752
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
751
753
  `;
752
- // --- CREATE_METHOD_SCRIPT_TEMPLATE uses FIND_OBJECT_BY_PATH_PYTHON_SNIPPET ---
753
- const CREATE_METHOD_SCRIPT_TEMPLATE = `
754
- import sys, scriptengine as script_engine, os, traceback
755
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
756
- ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
757
- PARENT_POU_FULL_PATH = "{PARENT_POU_FULL_PATH}" # e.g., "Application/MyFB"
758
- METHOD_NAME = "{METHOD_NAME}"
759
- RETURN_TYPE = "{RETURN_TYPE}" # Can be empty string for no return type
760
- # Optional: Language
761
- # LANG_GUID_STR = "{LANG_GUID_STR}" # Example if needed
762
-
763
- try:
764
- print("DEBUG: create_method script: ParentPOU='%s', Name='%s', ReturnType='%s', Project='%s'" % (PARENT_POU_FULL_PATH, METHOD_NAME, RETURN_TYPE, PROJECT_FILE_PATH))
765
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
766
- if not PARENT_POU_FULL_PATH: raise ValueError("Parent POU full path empty.")
767
- if not METHOD_NAME: raise ValueError("Method name empty.")
768
- # RETURN_TYPE can be empty
769
-
770
- # Find the parent POU object
771
- parent_pou_object = find_object_by_path_robust(primary_project, PARENT_POU_FULL_PATH, "parent POU")
772
- if not parent_pou_object: raise ValueError("Parent POU object not found: %s" % PARENT_POU_FULL_PATH)
773
-
774
- parent_pou_name = getattr(parent_pou_object, 'get_name', lambda: PARENT_POU_FULL_PATH)()
775
- print("DEBUG: Found Parent POU object: %s" % parent_pou_name)
776
-
777
- # Check if parent object supports creating methods (should implement ScriptIecLanguageMemberContainer)
778
- if not hasattr(parent_pou_object, 'create_method'):
779
- raise TypeError("Parent object '%s' of type %s does not support create_method." % (parent_pou_name, type(parent_pou_object).__name__))
780
-
781
- # Default language to None (usually ST)
782
- lang_guid = None
783
- # Use None if RETURN_TYPE is empty string, otherwise use the string
784
- actual_return_type = RETURN_TYPE if RETURN_TYPE else None
785
- print("DEBUG: Calling create_method: Name='%s', ReturnType=%s, Lang=%s" % (METHOD_NAME, actual_return_type, lang_guid))
786
-
787
- # Call the create_method method ON THE PARENT POU
788
- new_method_object = parent_pou_object.create_method(
789
- name=METHOD_NAME,
790
- return_type=actual_return_type,
791
- language=lang_guid # Pass None to use default
792
- )
793
-
794
- if new_method_object:
795
- new_meth_name = getattr(new_method_object, 'get_name', lambda: METHOD_NAME)()
796
- print("DEBUG: Method object created: %s" % new_meth_name)
797
-
798
- # --- SAVE THE PROJECT TO PERSIST THE NEW METHOD OBJECT ---
799
- try:
800
- print("DEBUG: Saving Project (after method creation)...")
801
- primary_project.save()
802
- print("DEBUG: Project saved successfully after method creation.")
803
- except Exception as save_err:
804
- print("ERROR: Failed to save Project after creating method: %s" % save_err)
805
- detailed_error = traceback.format_exc()
806
- error_message = "Error saving Project after creating method '%s': %s\\n%s" % (METHOD_NAME, save_err, detailed_error)
807
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
808
- # --- END SAVING ---
809
-
810
- print("Method Created: %s" % new_meth_name)
811
- print("Parent POU: %s" % PARENT_POU_FULL_PATH)
812
- print("Return Type: %s" % (RETURN_TYPE if RETURN_TYPE else "(None)"))
813
- print("SCRIPT_SUCCESS: Method created successfully."); sys.exit(0)
814
- else:
815
- error_message = "Failed to create method '%s' under '%s'. create_method returned None." % (METHOD_NAME, parent_pou_name)
816
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
817
-
818
- except Exception as e:
819
- detailed_error = traceback.format_exc()
820
- error_message = "Error creating method '%s' under POU '%s' in project '%s': %s\\n%s" % (METHOD_NAME, PARENT_POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
821
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
754
+ const CREATE_METHOD_SCRIPT_TEMPLATE = `
755
+ import sys, scriptengine as script_engine, os, traceback
756
+ ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
757
+ ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
758
+ PARENT_POU_FULL_PATH = "{PARENT_POU_FULL_PATH}" # e.g., "Application/MyFB"
759
+ METHOD_NAME = "{METHOD_NAME}"
760
+ RETURN_TYPE = "{RETURN_TYPE}" # Can be empty string for no return type
761
+ # Optional: Language
762
+ # LANG_GUID_STR = "{LANG_GUID_STR}" # Example if needed
763
+
764
+ try:
765
+ print("DEBUG: create_method script: ParentPOU='%s', Name='%s', ReturnType='%s', Project='%s'" % (PARENT_POU_FULL_PATH, METHOD_NAME, RETURN_TYPE, PROJECT_FILE_PATH))
766
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
767
+ if not PARENT_POU_FULL_PATH: raise ValueError("Parent POU full path empty.")
768
+ if not METHOD_NAME: raise ValueError("Method name empty.")
769
+ # RETURN_TYPE can be empty
770
+
771
+ # Find the parent POU object
772
+ parent_pou_object = find_object_by_path_robust(primary_project, PARENT_POU_FULL_PATH, "parent POU")
773
+ if not parent_pou_object: raise ValueError("Parent POU object not found: %s" % PARENT_POU_FULL_PATH)
774
+
775
+ parent_pou_name = getattr(parent_pou_object, 'get_name', lambda: PARENT_POU_FULL_PATH)()
776
+ print("DEBUG: Found Parent POU object: %s" % parent_pou_name)
777
+
778
+ # Check if parent object supports creating methods (should implement ScriptIecLanguageMemberContainer)
779
+ if not hasattr(parent_pou_object, 'create_method'):
780
+ raise TypeError("Parent object '%s' of type %s does not support create_method." % (parent_pou_name, type(parent_pou_object).__name__))
781
+
782
+ # Default language to None (usually ST)
783
+ lang_guid = None
784
+ # Use None if RETURN_TYPE is empty string, otherwise use the string
785
+ actual_return_type = RETURN_TYPE if RETURN_TYPE else None
786
+ print("DEBUG: Calling create_method: Name='%s', ReturnType=%s, Lang=%s" % (METHOD_NAME, actual_return_type, lang_guid))
787
+
788
+ # Call the create_method method ON THE PARENT POU
789
+ new_method_object = parent_pou_object.create_method(
790
+ name=METHOD_NAME,
791
+ return_type=actual_return_type,
792
+ language=lang_guid # Pass None to use default
793
+ )
794
+
795
+ if new_method_object:
796
+ new_meth_name = getattr(new_method_object, 'get_name', lambda: METHOD_NAME)()
797
+ print("DEBUG: Method object created: %s" % new_meth_name)
798
+
799
+ # --- SAVE THE PROJECT TO PERSIST THE NEW METHOD OBJECT ---
800
+ try:
801
+ print("DEBUG: Saving Project (after method creation)...")
802
+ primary_project.save()
803
+ print("DEBUG: Project saved successfully after method creation.")
804
+ except Exception as save_err:
805
+ print("ERROR: Failed to save Project after creating method: %s" % save_err)
806
+ detailed_error = traceback.format_exc()
807
+ error_message = "Error saving Project after creating method '%s': %s\\n%s" % (METHOD_NAME, save_err, detailed_error)
808
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
809
+ # --- END SAVING ---
810
+
811
+ print("Method Created: %s" % new_meth_name)
812
+ print("Parent POU: %s" % PARENT_POU_FULL_PATH)
813
+ print("Return Type: %s" % (RETURN_TYPE if RETURN_TYPE else "(None)"))
814
+ print("SCRIPT_SUCCESS: Method created successfully."); sys.exit(0)
815
+ else:
816
+ error_message = "Failed to create method '%s' under '%s'. create_method returned None." % (METHOD_NAME, parent_pou_name)
817
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
818
+
819
+ except Exception as e:
820
+ detailed_error = traceback.format_exc()
821
+ error_message = "Error creating method '%s' under POU '%s' in project '%s': %s\\n%s" % (METHOD_NAME, PARENT_POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
822
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
822
823
  `;
823
- // (COMPILE_PROJECT_SCRIPT_TEMPLATE remains the same)
824
- const COMPILE_PROJECT_SCRIPT_TEMPLATE = `
825
- import sys, scriptengine as script_engine, os, traceback
826
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
827
- try:
828
- print("DEBUG: compile_project script: Project='%s'" % PROJECT_FILE_PATH)
829
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
830
- project_name = os.path.basename(PROJECT_FILE_PATH)
831
- target_app = None
832
- app_name = "N/A"
833
-
834
- # Try getting active application first
835
- try:
836
- target_app = primary_project.active_application
837
- if target_app:
838
- app_name = getattr(target_app, 'get_name', lambda: "Unnamed App (Active)")()
839
- print("DEBUG: Found active application: %s" % app_name)
840
- except Exception as active_err:
841
- print("WARN: Could not get active application: %s. Searching..." % active_err)
842
-
843
- # If no active app, search for the first one
844
- if not target_app:
845
- print("DEBUG: Searching for first compilable application...")
846
- apps = []
847
- try:
848
- # Search recursively through all project objects
849
- all_children = primary_project.get_children(True)
850
- for child in all_children:
851
- # Check using the marker property and if build method exists
852
- if hasattr(child, 'is_application') and child.is_application and hasattr(child, 'build'):
853
- app_name_found = getattr(child, 'get_name', lambda: "Unnamed App")()
854
- print("DEBUG: Found potential application object: %s" % app_name_found)
855
- apps.append(child)
856
- break # Take the first one found
857
- except Exception as find_err: print("WARN: Error finding application object: %s" % find_err)
858
-
859
- if not apps: raise RuntimeError("No compilable application found in project '%s'" % project_name)
860
- target_app = apps[0]
861
- app_name = getattr(target_app, 'get_name', lambda: "Unnamed App (First Found)")()
862
- print("WARN: Compiling first found application: %s" % app_name)
863
-
864
- print("DEBUG: Calling build() on app '%s'..." % app_name)
865
- if not hasattr(target_app, 'build'):
866
- raise TypeError("Selected object '%s' is not an application or doesn't support build()." % app_name)
867
-
868
- # Execute the build
869
- target_app.build();
870
- print("DEBUG: Build command executed for application '%s'." % app_name)
871
-
872
- # Check messages is harder without direct access to message store from script.
873
- # Rely on CODESYS UI or log output for now.
874
- print("Compile Initiated For Application: %s" % app_name); print("In Project: %s" % project_name)
875
- print("SCRIPT_SUCCESS: Application compilation initiated."); sys.exit(0)
876
- except Exception as e:
877
- detailed_error = traceback.format_exc()
878
- error_message = "Error initiating compilation for project %s: %s\\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
879
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
824
+ const COMPILE_PROJECT_SCRIPT_TEMPLATE = `
825
+ import sys, scriptengine as script_engine, os, traceback
826
+ ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
827
+ try:
828
+ print("DEBUG: compile_project script: Project='%s'" % PROJECT_FILE_PATH)
829
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
830
+ project_name = os.path.basename(PROJECT_FILE_PATH)
831
+ target_app = None
832
+ app_name = "N/A"
833
+
834
+ # Try getting active application first
835
+ try:
836
+ target_app = primary_project.active_application
837
+ if target_app:
838
+ app_name = getattr(target_app, 'get_name', lambda: "Unnamed App (Active)")()
839
+ print("DEBUG: Found active application: %s" % app_name)
840
+ except Exception as active_err:
841
+ print("WARN: Could not get active application: %s. Searching..." % active_err)
842
+
843
+ # If no active app, search for the first one
844
+ if not target_app:
845
+ print("DEBUG: Searching for first compilable application...")
846
+ apps = []
847
+ try:
848
+ # Search recursively through all project objects
849
+ all_children = primary_project.get_children(True)
850
+ for child in all_children:
851
+ # Check using the marker property and if build method exists
852
+ if hasattr(child, 'is_application') and child.is_application and hasattr(child, 'build'):
853
+ app_name_found = getattr(child, 'get_name', lambda: "Unnamed App")()
854
+ print("DEBUG: Found potential application object: %s" % app_name_found)
855
+ apps.append(child)
856
+ break # Take the first one found
857
+ except Exception as find_err: print("WARN: Error finding application object: %s" % find_err)
858
+
859
+ if not apps: raise RuntimeError("No compilable application found in project '%s'" % project_name)
860
+ target_app = apps[0]
861
+ app_name = getattr(target_app, 'get_name', lambda: "Unnamed App (First Found)")()
862
+ print("WARN: Compiling first found application: %s" % app_name)
863
+
864
+ print("DEBUG: Calling build() on app '%s'..." % app_name)
865
+ if not hasattr(target_app, 'build'):
866
+ raise TypeError("Selected object '%s' is not an application or doesn't support build()." % app_name)
867
+
868
+ # Execute the build
869
+ target_app.build();
870
+ print("DEBUG: Build command executed for application '%s'." % app_name)
871
+
872
+ # Check messages is harder without direct access to message store from script.
873
+ # Rely on CODESYS UI or log output for now.
874
+ print("Compile Initiated For Application: %s" % app_name); print("In Project: %s" % project_name)
875
+ print("SCRIPT_SUCCESS: Application compilation initiated."); sys.exit(0)
876
+ except Exception as e:
877
+ detailed_error = traceback.format_exc()
878
+ error_message = "Error initiating compilation for project %s: %s\\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
879
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
880
880
  `;
881
- // (GET_PROJECT_STRUCTURE_SCRIPT_TEMPLATE remains the same)
882
- const GET_PROJECT_STRUCTURE_SCRIPT_TEMPLATE = `
883
- import sys, scriptengine as script_engine, os, traceback
884
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
885
- # FIND_OBJECT_BY_PATH_PYTHON_SNIPPET is NOT needed here, we start from project root.
886
-
887
- def get_object_structure(obj, indent=0, max_depth=10): # Add max_depth
888
- lines = []; indent_str = " " * indent
889
- if indent > max_depth:
890
- lines.append("%s- Max recursion depth reached." % indent_str)
891
- return lines
892
- try:
893
- name = "Unnamed"; obj_type = type(obj).__name__
894
- guid_str = ""
895
- folder_str = ""
896
- try:
897
- name = getattr(obj, 'get_name', lambda: "Unnamed")() or "Unnamed" # Safer get_name
898
- if hasattr(obj, 'guid'): guid_str = " {%s}" % obj.guid
899
- if hasattr(obj, 'is_folder') and obj.is_folder: folder_str = " [Folder]"
900
- except Exception as name_err:
901
- print("WARN: Error getting name/guid/folder status for an object: %s" % name_err)
902
- name = "!!! Error Getting Name !!!"
903
-
904
- lines.append("%s- %s (%s)%s%s" % (indent_str, name, obj_type, folder_str, guid_str))
905
-
906
- # Get children only if the object potentially has them
907
- children = []
908
- can_have_children = hasattr(obj, 'get_children') and (
909
- not hasattr(obj, 'is_folder') or # If it's not clear if it's a folder (e.g., project root)
910
- (hasattr(obj, 'is_folder') and obj.is_folder) or # If it is a folder
911
- # Add known container types explicitly, check marker interfaces too
912
- hasattr(obj, 'is_project') or hasattr(obj, 'is_application') or hasattr(obj, 'is_device') or hasattr(obj,'is_pou')
913
- )
914
-
915
- if can_have_children:
916
- try:
917
- children = obj.get_children(False)
918
- # print("DEBUG: %s has %d children" % (name, len(children))) # Verbose
919
- except Exception as get_child_err:
920
- lines.append("%s ERROR getting children: %s" % (indent_str, get_child_err))
921
- # traceback.print_exc() # Optional
922
-
923
- for child in children:
924
- lines.extend(get_object_structure(child, indent + 1, max_depth)) # Recurse
925
-
926
- except Exception as e:
927
- lines.append("%s- Error processing node: %s" % (indent_str, e))
928
- traceback.print_exc() # Print detailed error for this node
929
- return lines
930
- try:
931
- print("DEBUG: Getting structure for: %s" % PROJECT_FILE_PATH)
932
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
933
- project_name = os.path.basename(PROJECT_FILE_PATH)
934
- print("DEBUG: Getting structure for project: %s" % project_name)
935
- # Use the project object obtained from ensure_project_open
936
- structure_list = get_object_structure(primary_project, max_depth=15) # Set a reasonable depth
937
- structure_output = "\\n".join(structure_list)
938
- # Ensure markers are printed distinctly
939
- print("\\n--- PROJECT STRUCTURE START ---")
940
- print(structure_output)
941
- print("--- PROJECT STRUCTURE END ---\\n")
942
- print("SCRIPT_SUCCESS: Project structure retrieved."); sys.exit(0)
943
- except Exception as e:
944
- detailed_error = traceback.format_exc()
945
- error_message = "Error getting structure for %s: %s\\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
946
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
881
+ const GET_PROJECT_STRUCTURE_SCRIPT_TEMPLATE = `
882
+ import sys, scriptengine as script_engine, os, traceback
883
+ ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
884
+ # FIND_OBJECT_BY_PATH_PYTHON_SNIPPET is NOT needed here, we start from project root.
885
+
886
+ def get_object_structure(obj, indent=0, max_depth=10): # Add max_depth
887
+ lines = []; indent_str = " " * indent
888
+ if indent > max_depth:
889
+ lines.append("%s- Max recursion depth reached." % indent_str)
890
+ return lines
891
+ try:
892
+ name = "Unnamed"; obj_type = type(obj).__name__
893
+ guid_str = ""
894
+ folder_str = ""
895
+ try:
896
+ name = getattr(obj, 'get_name', lambda: "Unnamed")() or "Unnamed" # Safer get_name
897
+ if hasattr(obj, 'guid'): guid_str = " {%s}" % obj.guid
898
+ if hasattr(obj, 'is_folder') and obj.is_folder: folder_str = " [Folder]"
899
+ except Exception as name_err:
900
+ print("WARN: Error getting name/guid/folder status for an object: %s" % name_err)
901
+ name = "!!! Error Getting Name !!!"
902
+
903
+ lines.append("%s- %s (%s)%s%s" % (indent_str, name, obj_type, folder_str, guid_str))
904
+
905
+ # Get children only if the object potentially has them
906
+ children = []
907
+ can_have_children = hasattr(obj, 'get_children') and (
908
+ not hasattr(obj, 'is_folder') or # If it's not clear if it's a folder (e.g., project root)
909
+ (hasattr(obj, 'is_folder') and obj.is_folder) or # If it is a folder
910
+ # Add known container types explicitly, check marker interfaces too
911
+ hasattr(obj, 'is_project') or hasattr(obj, 'is_application') or hasattr(obj, 'is_device') or hasattr(obj,'is_pou')
912
+ )
913
+
914
+ if can_have_children:
915
+ try:
916
+ children = obj.get_children(False)
917
+ # print("DEBUG: %s has %d children" % (name, len(children))) # Verbose
918
+ except Exception as get_child_err:
919
+ lines.append("%s ERROR getting children: %s" % (indent_str, get_child_err))
920
+ # traceback.print_exc() # Optional
921
+
922
+ for child in children:
923
+ lines.extend(get_object_structure(child, indent + 1, max_depth)) # Recurse
924
+
925
+ except Exception as e:
926
+ lines.append("%s- Error processing node: %s" % (indent_str, e))
927
+ traceback.print_exc() # Print detailed error for this node
928
+ return lines
929
+ try:
930
+ print("DEBUG: Getting structure for: %s" % PROJECT_FILE_PATH)
931
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
932
+ project_name = os.path.basename(PROJECT_FILE_PATH)
933
+ print("DEBUG: Getting structure for project: %s" % project_name)
934
+ # Use the project object obtained from ensure_project_open
935
+ structure_list = get_object_structure(primary_project, max_depth=15) # Set a reasonable depth
936
+ structure_output = "\\n".join(structure_list)
937
+ # Ensure markers are printed distinctly
938
+ print("\\n--- PROJECT STRUCTURE START ---")
939
+ print(structure_output)
940
+ print("--- PROJECT STRUCTURE END ---\\n")
941
+ print("SCRIPT_SUCCESS: Project structure retrieved."); sys.exit(0)
942
+ except Exception as e:
943
+ detailed_error = traceback.format_exc()
944
+ error_message = "Error getting structure for %s: %s\\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
945
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
947
946
  `;
948
- // (GET_POU_CODE_SCRIPT_TEMPLATE uses FIND_OBJECT_BY_PATH_PYTHON_SNIPPET)
949
- const GET_POU_CODE_SCRIPT_TEMPLATE = `
950
- import sys, scriptengine as script_engine, os, traceback
951
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
952
- ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
953
- POU_FULL_PATH = "{POU_FULL_PATH}"; CODE_START_MARKER = "### POU CODE START ###"; CODE_END_MARKER = "### POU CODE END ###"
954
- DECL_START_MARKER = "### POU DECLARATION START ###"; DECL_END_MARKER = "### POU DECLARATION END ###"
955
- IMPL_START_MARKER = "### POU IMPLEMENTATION START ###"; IMPL_END_MARKER = "### POU IMPLEMENTATION END ###"
956
-
957
- try:
958
- print("DEBUG: Getting code: POU_FULL_PATH='%s', Project='%s'" % (POU_FULL_PATH, PROJECT_FILE_PATH))
959
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
960
- if not POU_FULL_PATH: raise ValueError("POU full path empty.")
961
-
962
- # Find the target POU/Method/Property object
963
- target_object = find_object_by_path_robust(primary_project, POU_FULL_PATH, "target object")
964
- if not target_object: raise ValueError("Target object not found using path: %s" % POU_FULL_PATH)
965
-
966
- target_name = getattr(target_object, 'get_name', lambda: POU_FULL_PATH)()
967
- print("DEBUG: Found target object: %s" % target_name)
968
-
969
- declaration_code = ""; implementation_code = ""
970
-
971
- # --- Get Declaration Part ---
972
- if hasattr(target_object, 'textual_declaration'):
973
- decl_obj = target_object.textual_declaration
974
- if decl_obj and hasattr(decl_obj, 'text'):
975
- try:
976
- declaration_code = decl_obj.text
977
- print("DEBUG: Got declaration text.")
978
- except Exception as decl_read_err:
979
- print("ERROR: Failed to read declaration text: %s" % decl_read_err)
980
- declaration_code = "/* ERROR reading declaration: %s */" % decl_read_err
981
- else:
982
- print("WARN: textual_declaration exists but is None or has no 'text' attribute.")
983
- else:
984
- print("WARN: No textual_declaration attribute.")
985
-
986
- # --- Get Implementation Part ---
987
- if hasattr(target_object, 'textual_implementation'):
988
- impl_obj = target_object.textual_implementation
989
- if impl_obj and hasattr(impl_obj, 'text'):
990
- try:
991
- implementation_code = impl_obj.text
992
- print("DEBUG: Got implementation text.")
993
- except Exception as impl_read_err:
994
- print("ERROR: Failed to read implementation text: %s" % impl_read_err)
995
- implementation_code = "/* ERROR reading implementation: %s */" % impl_read_err
996
- else:
997
- print("WARN: textual_implementation exists but is None or has no 'text' attribute.")
998
- else:
999
- print("WARN: No textual_implementation attribute.")
1000
-
1001
-
1002
- print("Code retrieved for: %s" % target_name)
1003
- # Print declaration between markers, ensuring markers are on separate lines
1004
- print("\\n" + DECL_START_MARKER)
1005
- print(declaration_code)
1006
- print(DECL_END_MARKER + "\\n")
1007
- # Print implementation between markers
1008
- print(IMPL_START_MARKER)
1009
- print(implementation_code)
1010
- print(IMPL_END_MARKER + "\\n")
1011
-
1012
- # --- LEGACY MARKERS for backward compatibility if needed ---
1013
- # Combine both for old marker format, adding a separator line
1014
- # legacy_combined_code = declaration_code + "\\n\\n// Implementation\\n" + implementation_code
1015
- # print(CODE_START_MARKER); print(legacy_combined_code); print(CODE_END_MARKER)
1016
- # --- END LEGACY ---
1017
-
1018
- print("SCRIPT_SUCCESS: Code retrieved."); sys.exit(0)
1019
- except Exception as e:
1020
- detailed_error = traceback.format_exc()
1021
- error_message = "Error getting code for object '%s' in project '%s': %s\\n%s" % (POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
1022
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
947
+ const GET_POU_CODE_SCRIPT_TEMPLATE = `
948
+ import sys, scriptengine as script_engine, os, traceback
949
+ ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
950
+ ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
951
+ POU_FULL_PATH = "{POU_FULL_PATH}"; CODE_START_MARKER = "### POU CODE START ###"; CODE_END_MARKER = "### POU CODE END ###"
952
+ DECL_START_MARKER = "### POU DECLARATION START ###"; DECL_END_MARKER = "### POU DECLARATION END ###"
953
+ IMPL_START_MARKER = "### POU IMPLEMENTATION START ###"; IMPL_END_MARKER = "### POU IMPLEMENTATION END ###"
954
+
955
+ try:
956
+ print("DEBUG: Getting code: POU_FULL_PATH='%s', Project='%s'" % (POU_FULL_PATH, PROJECT_FILE_PATH))
957
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
958
+ if not POU_FULL_PATH: raise ValueError("POU full path empty.")
959
+
960
+ # Find the target POU/Method/Property object
961
+ target_object = find_object_by_path_robust(primary_project, POU_FULL_PATH, "target object")
962
+ if not target_object: raise ValueError("Target object not found using path: %s" % POU_FULL_PATH)
963
+
964
+ target_name = getattr(target_object, 'get_name', lambda: POU_FULL_PATH)()
965
+ print("DEBUG: Found target object: %s" % target_name)
966
+
967
+ declaration_code = ""; implementation_code = ""
968
+
969
+ # --- Get Declaration Part ---
970
+ if hasattr(target_object, 'textual_declaration'):
971
+ decl_obj = target_object.textual_declaration
972
+ if decl_obj and hasattr(decl_obj, 'text'):
973
+ try:
974
+ declaration_code = decl_obj.text
975
+ print("DEBUG: Got declaration text.")
976
+ except Exception as decl_read_err:
977
+ print("ERROR: Failed to read declaration text: %s" % decl_read_err)
978
+ declaration_code = "/* ERROR reading declaration: %s */" % decl_read_err
979
+ else:
980
+ print("WARN: textual_declaration exists but is None or has no 'text' attribute.")
981
+ else:
982
+ print("WARN: No textual_declaration attribute.")
983
+
984
+ # --- Get Implementation Part ---
985
+ if hasattr(target_object, 'textual_implementation'):
986
+ impl_obj = target_object.textual_implementation
987
+ if impl_obj and hasattr(impl_obj, 'text'):
988
+ try:
989
+ implementation_code = impl_obj.text
990
+ print("DEBUG: Got implementation text.")
991
+ except Exception as impl_read_err:
992
+ print("ERROR: Failed to read implementation text: %s" % impl_read_err)
993
+ implementation_code = "/* ERROR reading implementation: %s */" % impl_read_err
994
+ else:
995
+ print("WARN: textual_implementation exists but is None or has no 'text' attribute.")
996
+ else:
997
+ print("WARN: No textual_implementation attribute.")
998
+
999
+
1000
+ print("Code retrieved for: %s" % target_name)
1001
+ # Print declaration between markers, ensuring markers are on separate lines
1002
+ print("\\n" + DECL_START_MARKER)
1003
+ print(declaration_code)
1004
+ print(DECL_END_MARKER + "\\n")
1005
+ # Print implementation between markers
1006
+ print(IMPL_START_MARKER)
1007
+ print(implementation_code)
1008
+ print(IMPL_END_MARKER + "\\n")
1009
+
1010
+ # --- LEGACY MARKERS for backward compatibility if needed ---
1011
+ # Combine both for old marker format, adding a separator line
1012
+ # legacy_combined_code = declaration_code + "\\n\\n// Implementation\\n" + implementation_code
1013
+ # print(CODE_START_MARKER); print(legacy_combined_code); print(CODE_END_MARKER)
1014
+ # --- END LEGACY ---
1015
+
1016
+ print("SCRIPT_SUCCESS: Code retrieved."); sys.exit(0)
1017
+ except Exception as e:
1018
+ detailed_error = traceback.format_exc()
1019
+ error_message = "Error getting code for object '%s' in project '%s': %s\\n%s" % (POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
1020
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
1023
1021
  `;
1024
- // --- MCP Resources ---
1025
- // (Resource definitions remain the same)
1026
- server.resource("project-status", "codesys://project/status", (uri) => __awaiter(void 0, void 0, void 0, function* () {
1027
- var _a, _b, _c, _d, _e;
1028
- console.error(`Resource request: ${uri.href}`);
1029
- try {
1030
- const result = yield (0, codesys_interop_1.executeCodesysScript)(CHECK_STATUS_SCRIPT, codesysExePath, codesysProfileName);
1031
- const outputLines = result.output.split(/[\r\n]+/).filter(line => line.trim());
1032
- const statusData = {};
1033
- outputLines.forEach(line => { const match = line.match(/^([^:]+):\s*(.*)$/); if (match) {
1034
- statusData[match[1].trim()] = match[2].trim();
1035
- } });
1036
- const statusText = `CODESYS Status:\n - Scripting OK: ${(_a = statusData['Scripting OK']) !== null && _a !== void 0 ? _a : 'Unknown'}\n - Project Open: ${(_b = statusData['Project Open']) !== null && _b !== void 0 ? _b : 'Unknown'}\n - Project Name: ${(_c = statusData['Project Name']) !== null && _c !== void 0 ? _c : 'Unknown'}\n - Project Path: ${(_d = statusData['Project Path']) !== null && _d !== void 0 ? _d : 'N/A'}`;
1037
- // Determine error status based on script success AND scripting OK status
1038
- const isError = !result.success || ((_e = statusData['Scripting OK']) === null || _e === void 0 ? void 0 : _e.toLowerCase()) !== 'true';
1039
- return { contents: [{ uri: uri.href, text: statusText, contentType: "text/plain" }], isError: isError };
1040
- }
1041
- catch (error) {
1042
- console.error(`Error resource ${uri.href}:`, error);
1043
- return { contents: [{ uri: uri.href, text: `Failed status script: ${error.message}`, contentType: "text/plain" }], isError: true };
1044
- }
1045
- }));
1046
- const projectStructureTemplate = new mcp_js_1.ResourceTemplate("codesys://project/{+project_path}/structure", { list: undefined });
1047
- server.resource("project-structure", projectStructureTemplate, (uri, params) => __awaiter(void 0, void 0, void 0, function* () {
1048
- const projectPathParam = params.project_path;
1049
- if (typeof projectPathParam !== 'string') {
1050
- return { contents: [{ uri: uri.href, text: `Error: Invalid project path type (${typeof projectPathParam}).`, contentType: "text/plain" }], isError: true };
1051
- }
1052
- const projectPath = projectPathParam;
1053
- if (!projectPath) {
1054
- return { contents: [{ uri: uri.href, text: "Error: Project path missing.", contentType: "text/plain" }], isError: true };
1055
- }
1056
- console.error(`Resource request: project structure for ${projectPath}`);
1057
- try {
1058
- const absoluteProjPath = path.normalize(path.isAbsolute(projectPath) ? projectPath : path.join(WORKSPACE_DIR, projectPath));
1059
- // No need to check file existence here, ensure_project_open will handle it
1060
- // if (!(await fileExists(absoluteProjPath))) { return { contents: [{ uri: uri.href, text: `Error: Project file not found: ${absoluteProjPath}`, contentType: "text/plain" }], isError: true }; }
1061
- const escapedPathForPython = absoluteProjPath.replace(/\\/g, '\\\\');
1062
- const scriptContent = GET_PROJECT_STRUCTURE_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPathForPython);
1063
- const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName);
1064
- let structureText = `Error retrieving structure for ${absoluteProjPath}.\n\n${result.output}`;
1065
- let isError = !result.success;
1066
- if (result.success && result.output.includes("SCRIPT_SUCCESS")) {
1067
- const startMarker = "--- PROJECT STRUCTURE START ---";
1068
- const endMarker = "--- PROJECT STRUCTURE END ---";
1069
- const startIndex = result.output.indexOf(startMarker);
1070
- const endIndex = result.output.indexOf(endMarker);
1071
- if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex) {
1072
- // Extract text between markers, replace escaped newlines, trim whitespace
1073
- structureText = result.output.substring(startIndex + startMarker.length, endIndex).replace(/\\n/g, '\n').trim();
1022
+ // --- End Python Script Templates ---
1023
+ // --- Zod Schemas (moved for clarity before usage) ---
1024
+ const PouTypeEnum = zod_1.z.enum(["Program", "FunctionBlock", "Function"]);
1025
+ const ImplementationLanguageEnum = zod_1.z.enum(["ST", "LD", "FBD", "SFC", "IL", "CFC", "StructuredText", "LadderDiagram", "FunctionBlockDiagram", "SequentialFunctionChart", "InstructionList", "ContinuousFunctionChart"]);
1026
+ // --- End Zod Schemas ---
1027
+ // --- MCP Resources / Tools Definitions ---
1028
+ console.error("SERVER.TS: Defining Resources and Tools...");
1029
+ // --- Resources ---
1030
+ server.resource("project-status", "codesys://project/status", (uri) => __awaiter(this, void 0, void 0, function* () {
1031
+ var _a, _b, _c, _d, _e;
1032
+ console.error(`SERVER.TS Resource request: ${uri.href}`);
1033
+ try {
1034
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(CHECK_STATUS_SCRIPT, codesysExePath, codesysProfileName);
1035
+ const outputLines = result.output.split(/[\r\n]+/).filter(line => line.trim());
1036
+ const statusData = {};
1037
+ outputLines.forEach(line => { const match = line.match(/^([^:]+):\s*(.*)$/); if (match) {
1038
+ statusData[match[1].trim()] = match[2].trim();
1039
+ } });
1040
+ const statusText = `CODESYS Status:\n - Scripting OK: ${(_a = statusData['Scripting OK']) !== null && _a !== void 0 ? _a : 'Unknown'}\n - Project Open: ${(_b = statusData['Project Open']) !== null && _b !== void 0 ? _b : 'Unknown'}\n - Project Name: ${(_c = statusData['Project Name']) !== null && _c !== void 0 ? _c : 'Unknown'}\n - Project Path: ${(_d = statusData['Project Path']) !== null && _d !== void 0 ? _d : 'N/A'}`;
1041
+ const isError = !result.success || ((_e = statusData['Scripting OK']) === null || _e === void 0 ? void 0 : _e.toLowerCase()) !== 'true';
1042
+ // **** RETURN MCP STRUCTURE ****
1043
+ return { contents: [{ uri: uri.href, text: statusText, contentType: "text/plain" }], isError: isError };
1074
1044
  }
1075
- else {
1076
- console.error("Error: Could not find structure markers in script output.");
1077
- structureText = `Could not parse structure markers in output for ${absoluteProjPath}.\n\nOutput:\n${result.output}`;
1078
- isError = true;
1045
+ catch (error) {
1046
+ console.error(`Error resource ${uri.href}:`, error);
1047
+ // **** RETURN MCP STRUCTURE ****
1048
+ return { contents: [{ uri: uri.href, text: `Failed status script: ${error.message}`, contentType: "text/plain" }], isError: true };
1079
1049
  }
1080
- }
1081
- else {
1082
- isError = true;
1083
- } // Explicitly set error if script failed
1084
- return { contents: [{ uri: uri.href, text: structureText, contentType: "text/plain" }], isError: isError };
1085
- }
1086
- catch (error) {
1087
- console.error(`Error getting structure ${uri.href}:`, error);
1088
- return { contents: [{ uri: uri.href, text: `Failed structure script for '${projectPath}': ${error.message}`, contentType: "text/plain" }], isError: true };
1089
- }
1090
- }));
1091
- const pouCodeTemplate = new mcp_js_1.ResourceTemplate("codesys://project/{+project_path}/pou/{+pou_path}/code", { list: undefined });
1092
- server.resource("pou-code", pouCodeTemplate, (uri, params) => __awaiter(void 0, void 0, void 0, function* () {
1093
- const projectPathParam = params.project_path;
1094
- const pouPathParam = params.pou_path;
1095
- if (typeof projectPathParam !== 'string' || typeof pouPathParam !== 'string') {
1096
- return { contents: [{ uri: uri.href, text: "Error: Invalid project or POU path type.", contentType: "text/plain" }], isError: true };
1097
- }
1098
- const projectPath = projectPathParam;
1099
- const pouPath = pouPathParam;
1100
- if (!projectPath || !pouPath) {
1101
- return { contents: [{ uri: uri.href, text: "Error: Project or POU path missing.", contentType: "text/plain" }], isError: true };
1102
- }
1103
- console.error(`Resource request: POU code: Project='${projectPath}', POU='${pouPath}'`);
1104
- try {
1105
- const absoluteProjPath = path.normalize(path.isAbsolute(projectPath) ? projectPath : path.join(WORKSPACE_DIR, projectPath));
1106
- const sanitizedPouPath = String(pouPath).replace(/\\/g, '/').replace(/^\/+|\/+$/g, ''); // Normalize slashes, remove leading/trailing
1107
- // No need to check file existence here, ensure_project_open handles it
1108
- // if (!(await fileExists(absoluteProjPath))) { return { contents: [{ uri: uri.href, text: `Error: Project file not found: ${absoluteProjPath}`, contentType: "text/plain" }], isError: true }; }
1109
- const escapedProjPath = absoluteProjPath.replace(/\\/g, '\\\\');
1110
- let scriptContent = GET_POU_CODE_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedProjPath);
1111
- scriptContent = scriptContent.replace("{POU_FULL_PATH}", sanitizedPouPath); // Use normalized POU path
1112
- const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName);
1113
- let codeText = `Error retrieving code for object '${sanitizedPouPath}' in project '${absoluteProjPath}'.\n\n${result.output}`;
1114
- let isError = !result.success;
1115
- if (result.success && result.output.includes("SCRIPT_SUCCESS")) {
1116
- const declStartMarker = "### POU DECLARATION START ###";
1117
- const declEndMarker = "### POU DECLARATION END ###";
1118
- const implStartMarker = "### POU IMPLEMENTATION START ###";
1119
- const implEndMarker = "### POU IMPLEMENTATION END ###";
1120
- const declStartIdx = result.output.indexOf(declStartMarker);
1121
- const declEndIdx = result.output.indexOf(declEndMarker);
1122
- const implStartIdx = result.output.indexOf(implStartMarker);
1123
- const implEndIdx = result.output.indexOf(implEndMarker);
1124
- let declaration = "/* Declaration not found in output */";
1125
- let implementation = "/* Implementation not found in output */";
1126
- if (declStartIdx !== -1 && declEndIdx !== -1 && declStartIdx < declEndIdx) {
1127
- declaration = result.output.substring(declStartIdx + declStartMarker.length, declEndIdx).replace(/\\n/g, '\n').trim();
1050
+ }));
1051
+ // *** DEFINE TEMPLATES ***
1052
+ const projectStructureTemplate = new mcp_js_1.ResourceTemplate("codesys://project/{+project_path}/structure", { list: undefined });
1053
+ const pouCodeTemplate = new mcp_js_1.ResourceTemplate("codesys://project/{+project_path}/pou/{+pou_path}/code", { list: undefined });
1054
+ // *** END DEFINE TEMPLATES ***
1055
+ server.resource("project-structure", projectStructureTemplate, (uri, params) => __awaiter(this, void 0, void 0, function* () {
1056
+ // *** DEFINE VARIABLES (like projectPath) ***
1057
+ const projectPathParam = params.project_path;
1058
+ if (typeof projectPathParam !== 'string') {
1059
+ return { contents: [{ uri: uri.href, text: `Error: Invalid project path type (${typeof projectPathParam}).`, contentType: "text/plain" }], isError: true };
1128
1060
  }
1129
- else {
1130
- console.error(`WARN: Declaration markers not found correctly for ${sanitizedPouPath}`);
1061
+ const projectPath = projectPathParam; // Define projectPath
1062
+ if (!projectPath) {
1063
+ return { contents: [{ uri: uri.href, text: "Error: Project path missing.", contentType: "text/plain" }], isError: true };
1131
1064
  }
1132
- if (implStartIdx !== -1 && implEndIdx !== -1 && implStartIdx < implEndIdx) {
1133
- implementation = result.output.substring(implStartIdx + implStartMarker.length, implEndIdx).replace(/\\n/g, '\n').trim();
1065
+ // *** END DEFINE VARIABLES ***
1066
+ console.error(`Resource request: project structure for ${projectPath}`);
1067
+ try {
1068
+ const absoluteProjPath = path.normalize(path.isAbsolute(projectPath) ? projectPath : path.join(WORKSPACE_DIR, projectPath));
1069
+ const escapedPathForPython = absoluteProjPath.replace(/\\/g, '\\\\');
1070
+ // *** DEFINE scriptContent ***
1071
+ const scriptContent = GET_PROJECT_STRUCTURE_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPathForPython);
1072
+ // *** END DEFINE scriptContent ***
1073
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName);
1074
+ let structureText = `Error retrieving structure for ${absoluteProjPath}.\n\n${result.output}`;
1075
+ let isError = !result.success;
1076
+ if (result.success && result.output.includes("SCRIPT_SUCCESS")) {
1077
+ const startMarker = "--- PROJECT STRUCTURE START ---";
1078
+ const endMarker = "--- PROJECT STRUCTURE END ---";
1079
+ const startIndex = result.output.indexOf(startMarker);
1080
+ const endIndex = result.output.indexOf(endMarker);
1081
+ if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex) {
1082
+ structureText = result.output.substring(startIndex + startMarker.length, endIndex).replace(/\\n/g, '\n').trim();
1083
+ }
1084
+ else {
1085
+ console.error("Error: Could not find structure markers in script output.");
1086
+ structureText = `Could not parse structure markers in output for ${absoluteProjPath}.\n\nOutput:\n${result.output}`;
1087
+ isError = true;
1088
+ }
1089
+ }
1090
+ else {
1091
+ isError = true;
1092
+ }
1093
+ // **** RETURN MCP STRUCTURE ****
1094
+ return { contents: [{ uri: uri.href, text: structureText, contentType: "text/plain" }], isError: isError };
1134
1095
  }
1135
- else {
1136
- console.error(`WARN: Implementation markers not found correctly for ${sanitizedPouPath}`);
1096
+ catch (error) {
1097
+ console.error(`Error getting structure ${uri.href}:`, error);
1098
+ // **** RETURN MCP STRUCTURE ****
1099
+ return { contents: [{ uri: uri.href, text: `Failed structure script for '${projectPath}': ${error.message}`, contentType: "text/plain" }], isError: true };
1137
1100
  }
1138
- // Combine them for the output (adjust format as needed)
1139
- codeText = `// ----- Declaration -----\n${declaration}\n\n// ----- Implementation -----\n${implementation}`;
1140
- }
1141
- else {
1142
- isError = true;
1143
- }
1144
- return { contents: [{ uri: uri.href, text: codeText, contentType: "text/plain" }], isError: isError };
1145
- }
1146
- catch (error) {
1147
- console.error(`Error getting POU code ${uri.href}:`, error);
1148
- return { contents: [{ uri: uri.href, text: `Failed POU code script for '${pouPath}' in '${projectPath}': ${error.message}`, contentType: "text/plain" }], isError: true };
1149
- }
1150
- }));
1151
- // --- MCP Tools ---
1152
- server.tool("open_project", { filePath: zod_1.z.string().describe("Path to the project file.") }, (args) => __awaiter(void 0, void 0, void 0, function* () {
1153
- const { filePath } = args;
1154
- let absPath = path.normalize(path.isAbsolute(filePath) ? filePath : path.join(WORKSPACE_DIR, filePath));
1155
- console.error(`Tool call: open_project: ${absPath}`);
1156
- // File existence check is now handled by ensure_project_open within the script
1157
- // if (!(await fileExists(absPath))) { return { content: [{ type: "text", text: `Error: File not found: ${absPath}` }], isError: true }; }
1158
- try {
1159
- const escapedPath = absPath.replace(/\\/g, '\\\\');
1160
- const script = OPEN_PROJECT_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPath);
1161
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1162
- const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1163
- return { content: [{ type: "text", text: success ? `Project opened: ${absPath}` : `Failed open project ${absPath}. Output:\n${result.output}` }], isError: !success };
1164
- }
1165
- catch (e) {
1166
- console.error(`Error open_project ${absPath}: ${e}`);
1167
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1168
- }
1169
- }));
1170
- server.tool("create_project", { filePath: zod_1.z.string().describe("Path for new project.") }, (args) => __awaiter(void 0, void 0, void 0, function* () {
1171
- const { filePath } = args;
1172
- let absPath = path.normalize(path.isAbsolute(filePath) ? filePath : path.join(WORKSPACE_DIR, filePath));
1173
- console.error(`Tool call: create_project (copy template): ${absPath}`);
1174
- let templatePath = "";
1175
- try {
1176
- // Prioritize relative path first, as it's more common with portable installs
1177
- const baseDir = path.dirname(path.dirname(codesysExePath)); // Go up two levels from CODESYS.exe
1178
- templatePath = path.normalize(path.join(baseDir, 'Templates', 'Standard.project'));
1179
- if (yield fileExists(templatePath)) {
1180
- console.error(`DEBUG: Found template relative to exe: ${templatePath}`);
1181
- }
1182
- else {
1183
- // Fallback to ProgramData
1184
- console.error(`WARN: Template not found relative to exe: ${templatePath}. Trying ProgramData...`);
1185
- const programData = process.env.ALLUSERSPROFILE || process.env.ProgramData || 'C:\\ProgramData';
1186
- // Construct path carefully, might vary slightly between versions/installers
1187
- const possibleTemplateDir = path.join(programData, 'CODESYS', 'CODESYS', codesysProfileName, 'Templates'); // Common pattern
1188
- let potentialTemplatePath = path.normalize(path.join(possibleTemplateDir, 'Standard.project'));
1189
- if (yield fileExists(potentialTemplatePath)) {
1190
- templatePath = potentialTemplatePath;
1191
- console.error(`DEBUG: Found template in ProgramData: ${templatePath}`);
1101
+ }));
1102
+ server.resource("pou-code", pouCodeTemplate, (uri, params) => __awaiter(this, void 0, void 0, function* () {
1103
+ // *** DEFINE VARIABLES (like projectPath, pouPath) ***
1104
+ const projectPathParam = params.project_path;
1105
+ const pouPathParam = params.pou_path;
1106
+ if (typeof projectPathParam !== 'string' || typeof pouPathParam !== 'string') {
1107
+ return { contents: [{ uri: uri.href, text: "Error: Invalid project or POU path type.", contentType: "text/plain" }], isError: true };
1192
1108
  }
1193
- else {
1194
- // Try another common pattern without the profile name subdir under the second CODESYS
1195
- const alternativeTemplateDir = path.join(programData, 'CODESYS', 'Templates');
1196
- potentialTemplatePath = path.normalize(path.join(alternativeTemplateDir, 'Standard.project'));
1197
- if (yield fileExists(potentialTemplatePath)) {
1198
- templatePath = potentialTemplatePath;
1199
- console.error(`DEBUG: Found template in ProgramData (alternative): ${templatePath}`);
1109
+ const projectPath = projectPathParam; // Define projectPath
1110
+ const pouPath = pouPathParam; // Define pouPath
1111
+ if (!projectPath || !pouPath) {
1112
+ return { contents: [{ uri: uri.href, text: "Error: Project or POU path missing.", contentType: "text/plain" }], isError: true };
1113
+ }
1114
+ // *** END DEFINE VARIABLES ***
1115
+ console.error(`Resource request: POU code: Project='${projectPath}', POU='${pouPath}'`);
1116
+ try {
1117
+ const absoluteProjPath = path.normalize(path.isAbsolute(projectPath) ? projectPath : path.join(WORKSPACE_DIR, projectPath));
1118
+ const sanitizedPouPath = String(pouPath).replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
1119
+ const escapedProjPath = absoluteProjPath.replace(/\\/g, '\\\\');
1120
+ // *** DEFINE scriptContent ***
1121
+ let scriptContent = GET_POU_CODE_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedProjPath);
1122
+ scriptContent = scriptContent.replace("{POU_FULL_PATH}", sanitizedPouPath);
1123
+ // *** END DEFINE scriptContent ***
1124
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName);
1125
+ let codeText = `Error retrieving code for object '${sanitizedPouPath}' in project '${absoluteProjPath}'.\n\n${result.output}`;
1126
+ let isError = !result.success;
1127
+ if (result.success && result.output.includes("SCRIPT_SUCCESS")) {
1128
+ // ... (marker parsing logic using new markers) ...
1129
+ const declStartMarker = "### POU DECLARATION START ###";
1130
+ const declEndMarker = "### POU DECLARATION END ###";
1131
+ const implStartMarker = "### POU IMPLEMENTATION START ###";
1132
+ const implEndMarker = "### POU IMPLEMENTATION END ###";
1133
+ const declStartIdx = result.output.indexOf(declStartMarker);
1134
+ const declEndIdx = result.output.indexOf(declEndMarker);
1135
+ const implStartIdx = result.output.indexOf(implStartMarker);
1136
+ const implEndIdx = result.output.indexOf(implEndMarker);
1137
+ let declaration = "/* Declaration not found in output */";
1138
+ let implementation = "/* Implementation not found in output */";
1139
+ if (declStartIdx !== -1 && declEndIdx !== -1 && declStartIdx < declEndIdx) {
1140
+ declaration = result.output.substring(declStartIdx + declStartMarker.length, declEndIdx).replace(/\\n/g, '\n').trim();
1141
+ }
1142
+ else {
1143
+ console.error(`WARN: Declaration markers not found correctly for ${sanitizedPouPath}`);
1144
+ }
1145
+ if (implStartIdx !== -1 && implEndIdx !== -1 && implStartIdx < implEndIdx) {
1146
+ implementation = result.output.substring(implStartIdx + implStartMarker.length, implEndIdx).replace(/\\n/g, '\n').trim();
1147
+ }
1148
+ else {
1149
+ console.error(`WARN: Implementation markers not found correctly for ${sanitizedPouPath}`);
1150
+ }
1151
+ codeText = `// ----- Declaration -----\n${declaration}\n\n// ----- Implementation -----\n${implementation}`;
1152
+ // *** END MARKER PARSING ***
1200
1153
  }
1201
1154
  else {
1202
- throw new Error(`Standard template project file not found at relative path or ProgramData locations.`);
1155
+ isError = true;
1203
1156
  }
1157
+ // **** RETURN MCP STRUCTURE ****
1158
+ return { contents: [{ uri: uri.href, text: codeText, contentType: "text/plain" }], isError: isError };
1204
1159
  }
1205
- }
1206
- }
1207
- catch (e) {
1208
- console.error(`Template Error: ${e.message}`);
1209
- return { content: [{ type: "text", text: `Template Error: ${e.message}` }], isError: true };
1210
- }
1211
- try {
1212
- const escProjPath = absPath.replace(/\\/g, '\\\\');
1213
- const escTmplPath = templatePath.replace(/\\/g, '\\\\');
1214
- const script = CREATE_PROJECT_SCRIPT_TEMPLATE
1215
- .replace("{PROJECT_FILE_PATH}", escProjPath)
1216
- .replace("{TEMPLATE_PROJECT_PATH}", escTmplPath);
1217
- console.error(">>> create_project (copy-then-open): PREPARED SCRIPT:", script.substring(0, 500) + "..."); // Log start of script
1218
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1219
- console.error(">>> create_project (copy-then-open): EXECUTION RESULT:", JSON.stringify(result));
1220
- const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1221
- return { content: [{ type: "text", text: success ? `Project created from template: ${absPath}` : `Failed create project ${absPath} from template. Output:\n${result.output}` }], isError: !success };
1222
- }
1223
- catch (e) {
1224
- console.error(`Error create_project ${absPath}: ${e}`);
1225
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1226
- }
1227
- }));
1228
- server.tool("save_project", { projectFilePath: zod_1.z.string().describe("Path to project.") }, (args) => __awaiter(void 0, void 0, void 0, function* () {
1229
- const { projectFilePath } = args;
1230
- let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1231
- console.error(`Tool call: save_project: ${absPath}`);
1232
- // No need to check file existence here, ensure_project_open handles it
1233
- // if (!(await fileExists(absPath))) { return { content: [{ type: "text", text: `Error: File not found: ${absPath}` }], isError: true }; }
1234
- try {
1235
- const escapedPath = absPath.replace(/\\/g, '\\\\');
1236
- const script = SAVE_PROJECT_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPath);
1237
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1238
- const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1239
- return { content: [{ type: "text", text: success ? `Project saved: ${absPath}` : `Failed save project ${absPath}. Output:\n${result.output}` }], isError: !success };
1240
- }
1241
- catch (e) {
1242
- console.error(`Error save_project ${absPath}: ${e}`);
1243
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1244
- }
1245
- }));
1246
- server.tool("create_pou", {
1247
- projectFilePath: zod_1.z.string().describe("Path to project."),
1248
- name: zod_1.z.string().describe("Name for the new POU."),
1249
- type: PouTypeEnum.describe("Type of POU (Program, FunctionBlock, Function)."),
1250
- language: ImplementationLanguageEnum.describe("Implementation language (ST, LD, FBD, etc.). Will use default if not directly supported/mapped by script."),
1251
- parentPath: zod_1.z.string().describe("Relative path under project/application (e.g., 'Application' or 'MyFolder/SubFolder').")
1252
- }, (args) => __awaiter(void 0, void 0, void 0, function* () {
1253
- const { projectFilePath, name, type, language, parentPath } = args;
1254
- let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1255
- const sanParentPath = parentPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, ''); // Normalize parent path slashes
1256
- const sanName = name.trim(); // Trim whitespace from name
1257
- console.error(`Tool call: create_pou: Name='${sanName}', Type='${type}', Lang='${language}', Parent='${sanParentPath}', Project='${absPath}'`);
1258
- // No need for file exists check here
1259
- // if (!(await fileExists(absPath))) { return { content: [{ type: "text", text: `Error: File not found: ${absPath}` }], isError: true }; }
1260
- try {
1261
- const escProjPath = absPath.replace(/\\/g, '\\\\');
1262
- // Use the fixed CREATE_POU_SCRIPT_TEMPLATE
1263
- let script = CREATE_POU_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1264
- script = script.replace("{POU_NAME}", sanName);
1265
- script = script.replace("{POU_TYPE_STR}", type); // Pass the enum string directly
1266
- script = script.replace("{IMPL_LANGUAGE_STR}", language); // Pass language string
1267
- script = script.replace("{PARENT_PATH}", sanParentPath); // Use normalized path
1268
- console.error(">>> create_pou: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1269
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1270
- console.error(">>> create_pou: EXECUTION RESULT:", JSON.stringify(result));
1271
- const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1272
- return { content: [{ type: "text", text: success ? `POU '${sanName}' created in '${sanParentPath}' of ${absPath}. Project saved.` : `Failed create POU '${sanName}'. Output:\n${result.output}` }], isError: !success };
1273
- }
1274
- catch (e) {
1275
- console.error(`Error create_pou ${sanName} in ${absPath}: ${e}`);
1276
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1277
- }
1278
- }));
1279
- // **TOOL: set_pou_code**
1280
- server.tool("set_pou_code", {
1281
- projectFilePath: zod_1.z.string().describe("Path to project."),
1282
- pouPath: zod_1.z.string().describe("Relative POU/Method/Property path (e.g., 'Application/MyPOU' or 'MyFolder/MyFB/MyMethod')."),
1283
- declarationCode: zod_1.z.string().describe("Code for the declaration part (VAR...END_VAR).").optional(),
1284
- implementationCode: zod_1.z.string().describe("Code for the implementation logic part.").optional()
1285
- }, (args) => __awaiter(void 0, void 0, void 0, function* () {
1286
- const { projectFilePath, pouPath, declarationCode, implementationCode } = args;
1287
- // Basic validation: ensure at least one code part is provided
1288
- if (declarationCode === undefined && implementationCode === undefined) {
1289
- return { content: [{ type: "text", text: "Error: At least one of declarationCode or implementationCode must be provided." }], isError: true };
1290
- }
1291
- let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1292
- const sanPouPath = pouPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, ''); // Normalize POU path slashes
1293
- console.error(`Tool call: set_pou_code: Target='${sanPouPath}', Project='${absPath}'`);
1294
- // No need for file exists check here
1295
- // if (!(await fileExists(absPath))) { return { content: [{ type: "text", text: `Error: File not found: ${absPath}` }], isError: true }; }
1296
- try {
1297
- const escProjPath = absPath.replace(/\\/g, '\\\\');
1298
- // Escape content for Python triple-quoted strings
1299
- // Provide empty string if undefined, then escape
1300
- const sanDeclCode = (declarationCode !== null && declarationCode !== void 0 ? declarationCode : "").replace(/\\/g, '\\\\').replace(/"""/g, '\\"\\"\\"');
1301
- const sanImplCode = (implementationCode !== null && implementationCode !== void 0 ? implementationCode : "").replace(/\\/g, '\\\\').replace(/"""/g, '\\"\\"\\"');
1302
- // Use the SET_POU_CODE_SCRIPT_TEMPLATE
1303
- let script = SET_POU_CODE_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1304
- script = script.replace("{POU_FULL_PATH}", sanPouPath);
1305
- script = script.replace("{DECLARATION_CONTENT}", sanDeclCode);
1306
- script = script.replace("{IMPLEMENTATION_CONTENT}", sanImplCode);
1307
- console.error(">>> set_pou_code: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1308
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1309
- console.error(">>> set_pou_code: EXECUTION RESULT:", JSON.stringify(result));
1310
- const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1311
- return { content: [{ type: "text", text: success ? `Code set for '${sanPouPath}' in ${absPath}. Project saved.` : `Failed set code for '${sanPouPath}'. Output:\n${result.output}` }], isError: !success };
1312
- }
1313
- catch (e) {
1314
- console.error(`Error set_pou_code ${sanPouPath} in ${absPath}: ${e}`);
1315
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1316
- }
1317
- }));
1318
- // **NEW TOOL: create_property**
1319
- server.tool("create_property", {
1320
- projectFilePath: zod_1.z.string().describe("Path to the project file."),
1321
- parentPouPath: zod_1.z.string().describe("Relative path to the parent POU (e.g., 'Application/MyFB')."),
1322
- propertyName: zod_1.z.string().describe("Name of the new property."),
1323
- propertyType: zod_1.z.string().describe("Data type of the property (e.g., 'BOOL', 'INT', 'MyDUT').")
1324
- // language: z.string().optional().describe("Implementation language for Getter/Setter (e.g., 'ST'). Defaults to project default.") // Optional
1325
- }, (args) => __awaiter(void 0, void 0, void 0, function* () {
1326
- const { projectFilePath, parentPouPath, propertyName, propertyType } = args;
1327
- let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1328
- const sanParentPath = parentPouPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
1329
- const sanPropName = propertyName.trim();
1330
- const sanPropType = propertyType.trim();
1331
- console.error(`Tool call: create_property: Name='${sanPropName}', Type='${sanPropType}', ParentPOU='${sanParentPath}', Project='${absPath}'`);
1332
- // No file exists check needed here
1333
- if (!sanPropName || !sanPropType) {
1334
- return { content: [{ type: "text", text: `Error: Property name and type cannot be empty.` }], isError: true };
1335
- }
1336
- try {
1337
- const escProjPath = absPath.replace(/\\/g, '\\\\');
1338
- let script = CREATE_PROPERTY_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1339
- script = script.replace("{PARENT_POU_FULL_PATH}", sanParentPath);
1340
- script = script.replace("{PROPERTY_NAME}", sanPropName);
1341
- script = script.replace("{PROPERTY_TYPE}", sanPropType);
1342
- // Add language replacement here if you implement the optional language argument
1343
- console.error(">>> create_property: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1344
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1345
- console.error(">>> create_property: EXECUTION RESULT:", JSON.stringify(result));
1346
- const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1347
- return {
1348
- content: [{ type: "text", text: success ? `Property '${sanPropName}' created under '${sanParentPath}' in ${absPath}. Project saved.` : `Failed to create property '${sanPropName}'. Output:\n${result.output}` }],
1349
- isError: !success
1350
- };
1351
- }
1352
- catch (e) {
1353
- console.error(`Error create_property ${sanPropName} in ${absPath}: ${e}`);
1354
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1355
- }
1356
- }));
1357
- // **NEW TOOL: create_method**
1358
- server.tool("create_method", {
1359
- projectFilePath: zod_1.z.string().describe("Path to the project file."),
1360
- parentPouPath: zod_1.z.string().describe("Relative path to the parent POU (e.g., 'Application/MyFB')."),
1361
- methodName: zod_1.z.string().describe("Name of the new method."),
1362
- returnType: zod_1.z.string().optional().describe("Return type (e.g., 'BOOL', 'INT'). Leave empty or omit for no return value."),
1363
- // language: z.string().optional().describe("Implementation language (e.g., 'ST'). Defaults to project default.") // Optional
1364
- }, (args) => __awaiter(void 0, void 0, void 0, function* () {
1365
- const { projectFilePath, parentPouPath, methodName, returnType } = args;
1366
- let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1367
- const sanParentPath = parentPouPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
1368
- const sanMethName = methodName.trim();
1369
- const sanReturnType = (returnType !== null && returnType !== void 0 ? returnType : "").trim(); // Use empty string if undefined/null
1370
- console.error(`Tool call: create_method: Name='${sanMethName}', Return='${sanReturnType}', ParentPOU='${sanParentPath}', Project='${absPath}'`);
1371
- // No file exists check needed here
1372
- if (!sanMethName) {
1373
- return { content: [{ type: "text", text: `Error: Method name cannot be empty.` }], isError: true };
1374
- }
1375
- try {
1376
- const escProjPath = absPath.replace(/\\/g, '\\\\');
1377
- let script = CREATE_METHOD_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1378
- script = script.replace("{PARENT_POU_FULL_PATH}", sanParentPath);
1379
- script = script.replace("{METHOD_NAME}", sanMethName);
1380
- script = script.replace("{RETURN_TYPE}", sanReturnType); // Pass empty string if no return type
1381
- // Add language replacement here if needed
1382
- console.error(">>> create_method: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1383
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1384
- console.error(">>> create_method: EXECUTION RESULT:", JSON.stringify(result));
1385
- const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1386
- return {
1387
- content: [{ type: "text", text: success ? `Method '${sanMethName}' created under '${sanParentPath}' in ${absPath}. Project saved.` : `Failed to create method '${sanMethName}'. Output:\n${result.output}` }],
1388
- isError: !success
1389
- };
1390
- }
1391
- catch (e) {
1392
- console.error(`Error create_method ${sanMethName} in ${absPath}: ${e}`);
1393
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1394
- }
1395
- }));
1396
- // (compile_project tool remains the same)
1397
- server.tool("compile_project", { projectFilePath: zod_1.z.string().describe("Path to project.") }, (args) => __awaiter(void 0, void 0, void 0, function* () {
1398
- const { projectFilePath } = args;
1399
- let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1400
- console.error(`Tool call: compile_project: ${absPath}`);
1401
- // No file exists check needed here
1402
- // if (!(await fileExists(absPath))) { return { content: [{ type: "text", text: `Error: File not found: ${absPath}` }], isError: true }; }
1403
- try {
1404
- const escapedPath = absPath.replace(/\\/g, '\\\\');
1405
- const script = COMPILE_PROJECT_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPath);
1406
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1407
- const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1408
- // Add check for compile errors in output if possible/needed
1409
- // Regex might be more robust if format is consistent
1410
- const hasCompileErrors = result.output.includes("Compile complete --") && !/ 0 error\(s\),/.test(result.output);
1411
- let message = success ? `Compilation initiated for application in ${absPath}. Check CODESYS messages for results.` : `Failed initiating compilation for ${absPath}. Output:\n${result.output}`;
1412
- if (success && hasCompileErrors) {
1413
- message += " WARNING: Build command reported errors in the output log.";
1414
- console.warn("Compile project reported build errors in the output.");
1415
- // Optionally set isError = true here if compile errors should fail the tool call
1416
- // return { content: [{ type: "text", text: message }], isError: true };
1417
- }
1418
- return { content: [{ type: "text", text: message }], isError: !success };
1419
- }
1420
- catch (e) {
1421
- console.error(`Error compile_project ${absPath}: ${e}`);
1422
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1423
- }
1424
- }));
1425
- // --- Server Connection & Start ---
1426
- function startServer() {
1427
- return __awaiter(this, void 0, void 0, function* () {
1160
+ catch (error) {
1161
+ console.error(`Error getting POU code ${uri.href}:`, error);
1162
+ // **** RETURN MCP STRUCTURE ****
1163
+ return { contents: [{ uri: uri.href, text: `Failed POU code script for '${pouPath}' in '${projectPath}': ${error.message}`, contentType: "text/plain" }], isError: true };
1164
+ }
1165
+ }));
1166
+ // --- End Resources ---
1167
+ // --- Tools ---
1168
+ server.tool("open_project", { filePath: zod_1.z.string().describe("Path to the project file.") }, (args) => __awaiter(this, void 0, void 0, function* () {
1169
+ const { filePath } = args;
1170
+ let absPath = path.normalize(path.isAbsolute(filePath) ? filePath : path.join(WORKSPACE_DIR, filePath));
1171
+ console.error(`Tool call: open_project: ${absPath}`);
1172
+ try {
1173
+ const escapedPath = absPath.replace(/\\/g, '\\\\');
1174
+ // *** DEFINE script ***
1175
+ const script = OPEN_PROJECT_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPath);
1176
+ // *** END DEFINE script ***
1177
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1178
+ const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1179
+ // **** RETURN MCP STRUCTURE ****
1180
+ return { content: [{ type: "text", text: success ? `Project opened: ${absPath}` : `Failed open project ${absPath}. Output:\n${result.output}` }], isError: !success };
1181
+ }
1182
+ catch (e) {
1183
+ console.error(`Error open_project ${absPath}: ${e}`);
1184
+ // **** RETURN MCP STRUCTURE ****
1185
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1186
+ }
1187
+ }));
1188
+ server.tool("create_project", { filePath: zod_1.z.string().describe("Path for new project.") }, (args) => __awaiter(this, void 0, void 0, function* () {
1189
+ const { filePath } = args;
1190
+ let absPath = path.normalize(path.isAbsolute(filePath) ? filePath : path.join(WORKSPACE_DIR, filePath));
1191
+ console.error(`Tool call: create_project (copy template): ${absPath}`);
1192
+ let templatePath = "";
1193
+ try {
1194
+ // Template finding logic (same as before)
1195
+ const baseDir = path.dirname(path.dirname(codesysExePath));
1196
+ templatePath = path.normalize(path.join(baseDir, 'Templates', 'Standard.project'));
1197
+ if (!(yield fileExists(templatePath))) {
1198
+ console.error(`WARN: Template not found relative to exe: ${templatePath}. Trying ProgramData...`);
1199
+ const programData = process.env.ALLUSERSPROFILE || process.env.ProgramData || 'C:\\ProgramData';
1200
+ const possibleTemplateDir = path.join(programData, 'CODESYS', 'CODESYS', codesysProfileName, 'Templates');
1201
+ let potentialTemplatePath = path.normalize(path.join(possibleTemplateDir, 'Standard.project'));
1202
+ if (yield fileExists(potentialTemplatePath)) {
1203
+ templatePath = potentialTemplatePath;
1204
+ console.error(`DEBUG: Found template in ProgramData: ${templatePath}`);
1205
+ }
1206
+ else {
1207
+ const alternativeTemplateDir = path.join(programData, 'CODESYS', 'Templates');
1208
+ potentialTemplatePath = path.normalize(path.join(alternativeTemplateDir, 'Standard.project'));
1209
+ if (yield fileExists(potentialTemplatePath)) {
1210
+ templatePath = potentialTemplatePath;
1211
+ console.error(`DEBUG: Found template in ProgramData (alternative): ${templatePath}`);
1212
+ }
1213
+ else {
1214
+ throw new Error(`Standard template project file not found at relative path or ProgramData locations.`);
1215
+ }
1216
+ }
1217
+ }
1218
+ else {
1219
+ console.error(`DEBUG: Found template relative to exe: ${templatePath}`);
1220
+ }
1221
+ // *** END TEMPLATE FINDING ***
1222
+ }
1223
+ catch (e) {
1224
+ console.error(`Template Error: ${e.message}`);
1225
+ // **** RETURN MCP STRUCTURE ****
1226
+ return { content: [{ type: "text", text: `Template Error: ${e.message}` }], isError: true };
1227
+ }
1228
+ try {
1229
+ const escProjPath = absPath.replace(/\\/g, '\\\\');
1230
+ const escTmplPath = templatePath.replace(/\\/g, '\\\\');
1231
+ // *** DEFINE script ***
1232
+ const script = CREATE_PROJECT_SCRIPT_TEMPLATE
1233
+ .replace("{PROJECT_FILE_PATH}", escProjPath)
1234
+ .replace("{TEMPLATE_PROJECT_PATH}", escTmplPath);
1235
+ // *** END DEFINE script ***
1236
+ console.error(">>> create_project (copy-then-open): PREPARED SCRIPT:", script.substring(0, 500) + "...");
1237
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1238
+ console.error(">>> create_project (copy-then-open): EXECUTION RESULT:", JSON.stringify(result));
1239
+ const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1240
+ // **** RETURN MCP STRUCTURE ****
1241
+ return { content: [{ type: "text", text: success ? `Project created from template: ${absPath}` : `Failed create project ${absPath} from template. Output:\n${result.output}` }], isError: !success };
1242
+ }
1243
+ catch (e) {
1244
+ console.error(`Error create_project ${absPath}: ${e}`);
1245
+ // **** RETURN MCP STRUCTURE ****
1246
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1247
+ }
1248
+ }));
1249
+ server.tool("save_project", { projectFilePath: zod_1.z.string().describe("Path to project.") }, (args) => __awaiter(this, void 0, void 0, function* () {
1250
+ const { projectFilePath } = args;
1251
+ let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1252
+ console.error(`Tool call: save_project: ${absPath}`);
1253
+ try {
1254
+ const escapedPath = absPath.replace(/\\/g, '\\\\');
1255
+ // *** DEFINE script ***
1256
+ const script = SAVE_PROJECT_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPath);
1257
+ // *** END DEFINE script ***
1258
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1259
+ const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1260
+ // **** RETURN MCP STRUCTURE ****
1261
+ return { content: [{ type: "text", text: success ? `Project saved: ${absPath}` : `Failed save project ${absPath}. Output:\n${result.output}` }], isError: !success };
1262
+ }
1263
+ catch (e) {
1264
+ console.error(`Error save_project ${absPath}: ${e}`);
1265
+ // **** RETURN MCP STRUCTURE ****
1266
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1267
+ }
1268
+ }));
1269
+ server.tool("create_pou", {
1270
+ projectFilePath: zod_1.z.string().describe("Path to project."),
1271
+ name: zod_1.z.string().describe("Name for the new POU."),
1272
+ type: PouTypeEnum.describe("Type of POU (Program, FunctionBlock, Function)."),
1273
+ language: ImplementationLanguageEnum.describe("Implementation language (ST, LD, FBD, etc.). Will use default if not directly supported/mapped by script."),
1274
+ parentPath: zod_1.z.string().describe("Relative path under project/application (e.g., 'Application' or 'MyFolder/SubFolder').")
1275
+ }, (args) => __awaiter(this, void 0, void 0, function* () {
1276
+ const { projectFilePath, name, type, language, parentPath } = args;
1277
+ let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1278
+ const sanParentPath = parentPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
1279
+ const sanName = name.trim();
1280
+ console.error(`Tool call: create_pou: Name='${sanName}', Type='${type}', Lang='${language}', Parent='${sanParentPath}', Project='${absPath}'`);
1281
+ try {
1282
+ const escProjPath = absPath.replace(/\\/g, '\\\\');
1283
+ // *** DEFINE script ***
1284
+ let script = CREATE_POU_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1285
+ script = script.replace("{POU_NAME}", sanName);
1286
+ script = script.replace("{POU_TYPE_STR}", type);
1287
+ script = script.replace("{IMPL_LANGUAGE_STR}", language);
1288
+ script = script.replace("{PARENT_PATH}", sanParentPath);
1289
+ // *** END DEFINE script ***
1290
+ console.error(">>> create_pou: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1291
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1292
+ console.error(">>> create_pou: EXECUTION RESULT:", JSON.stringify(result));
1293
+ const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1294
+ // **** RETURN MCP STRUCTURE ****
1295
+ return { content: [{ type: "text", text: success ? `POU '${sanName}' created in '${sanParentPath}' of ${absPath}. Project saved.` : `Failed create POU '${sanName}'. Output:\n${result.output}` }], isError: !success };
1296
+ }
1297
+ catch (e) {
1298
+ console.error(`Error create_pou ${sanName} in ${absPath}: ${e}`);
1299
+ // **** RETURN MCP STRUCTURE ****
1300
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1301
+ }
1302
+ }));
1303
+ server.tool("set_pou_code", {
1304
+ projectFilePath: zod_1.z.string().describe("Path to project."),
1305
+ pouPath: zod_1.z.string().describe("Relative POU/Method/Property path (e.g., 'Application/MyPOU' or 'MyFolder/MyFB/MyMethod')."),
1306
+ declarationCode: zod_1.z.string().describe("Code for the declaration part (VAR...END_VAR).").optional(),
1307
+ implementationCode: zod_1.z.string().describe("Code for the implementation logic part.").optional()
1308
+ }, (args) => __awaiter(this, void 0, void 0, function* () {
1309
+ const { projectFilePath, pouPath, declarationCode, implementationCode } = args;
1310
+ if (declarationCode === undefined && implementationCode === undefined) {
1311
+ return { content: [{ type: "text", text: "Error: At least one of declarationCode or implementationCode must be provided." }], isError: true };
1312
+ }
1313
+ let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1314
+ const sanPouPath = pouPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
1315
+ console.error(`Tool call: set_pou_code: Target='${sanPouPath}', Project='${absPath}'`);
1316
+ try {
1317
+ const escProjPath = absPath.replace(/\\/g, '\\\\');
1318
+ // Escape content for Python triple-quoted strings
1319
+ const sanDeclCode = (declarationCode !== null && declarationCode !== void 0 ? declarationCode : "").replace(/\\/g, '\\\\').replace(/"""/g, '\\"\\"\\"');
1320
+ const sanImplCode = (implementationCode !== null && implementationCode !== void 0 ? implementationCode : "").replace(/\\/g, '\\\\').replace(/"""/g, '\\"\\"\\"');
1321
+ // *** DEFINE script ***
1322
+ let script = SET_POU_CODE_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1323
+ script = script.replace("{POU_FULL_PATH}", sanPouPath);
1324
+ script = script.replace("{DECLARATION_CONTENT}", sanDeclCode);
1325
+ script = script.replace("{IMPLEMENTATION_CONTENT}", sanImplCode);
1326
+ // *** END DEFINE script ***
1327
+ console.error(">>> set_pou_code: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1328
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1329
+ console.error(">>> set_pou_code: EXECUTION RESULT:", JSON.stringify(result));
1330
+ const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1331
+ // **** RETURN MCP STRUCTURE ****
1332
+ return { content: [{ type: "text", text: success ? `Code set for '${sanPouPath}' in ${absPath}. Project saved.` : `Failed set code for '${sanPouPath}'. Output:\n${result.output}` }], isError: !success };
1333
+ }
1334
+ catch (e) {
1335
+ console.error(`Error set_pou_code ${sanPouPath} in ${absPath}: ${e}`);
1336
+ // **** RETURN MCP STRUCTURE ****
1337
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1338
+ }
1339
+ }));
1340
+ server.tool("create_property", {
1341
+ projectFilePath: zod_1.z.string().describe("Path to the project file."),
1342
+ parentPouPath: zod_1.z.string().describe("Relative path to the parent POU (e.g., 'Application/MyFB')."),
1343
+ propertyName: zod_1.z.string().describe("Name of the new property."),
1344
+ propertyType: zod_1.z.string().describe("Data type of the property (e.g., 'BOOL', 'INT', 'MyDUT').")
1345
+ }, (args) => __awaiter(this, void 0, void 0, function* () {
1346
+ const { projectFilePath, parentPouPath, propertyName, propertyType } = args;
1347
+ let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1348
+ const sanParentPath = parentPouPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
1349
+ const sanPropName = propertyName.trim();
1350
+ const sanPropType = propertyType.trim();
1351
+ console.error(`Tool call: create_property: Name='${sanPropName}', Type='${sanPropType}', ParentPOU='${sanParentPath}', Project='${absPath}'`);
1352
+ if (!sanPropName || !sanPropType) {
1353
+ // **** RETURN MCP STRUCTURE ****
1354
+ return { content: [{ type: "text", text: `Error: Property name and type cannot be empty.` }], isError: true };
1355
+ }
1356
+ try {
1357
+ const escProjPath = absPath.replace(/\\/g, '\\\\');
1358
+ // *** DEFINE script ***
1359
+ let script = CREATE_PROPERTY_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1360
+ script = script.replace("{PARENT_POU_FULL_PATH}", sanParentPath);
1361
+ script = script.replace("{PROPERTY_NAME}", sanPropName);
1362
+ script = script.replace("{PROPERTY_TYPE}", sanPropType);
1363
+ // *** END DEFINE script ***
1364
+ console.error(">>> create_property: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1365
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1366
+ console.error(">>> create_property: EXECUTION RESULT:", JSON.stringify(result));
1367
+ const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1368
+ // **** RETURN MCP STRUCTURE ****
1369
+ return {
1370
+ content: [{ type: "text", text: success ? `Property '${sanPropName}' created under '${sanParentPath}' in ${absPath}. Project saved.` : `Failed to create property '${sanPropName}'. Output:\n${result.output}` }],
1371
+ isError: !success
1372
+ };
1373
+ }
1374
+ catch (e) {
1375
+ console.error(`Error create_property ${sanPropName} in ${absPath}: ${e}`);
1376
+ // **** RETURN MCP STRUCTURE ****
1377
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1378
+ }
1379
+ }));
1380
+ server.tool("create_method", {
1381
+ projectFilePath: zod_1.z.string().describe("Path to the project file."),
1382
+ parentPouPath: zod_1.z.string().describe("Relative path to the parent POU (e.g., 'Application/MyFB')."),
1383
+ methodName: zod_1.z.string().describe("Name of the new method."),
1384
+ returnType: zod_1.z.string().optional().describe("Return type (e.g., 'BOOL', 'INT'). Leave empty or omit for no return value."),
1385
+ }, (args) => __awaiter(this, void 0, void 0, function* () {
1386
+ const { projectFilePath, parentPouPath, methodName, returnType } = args;
1387
+ let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1388
+ const sanParentPath = parentPouPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
1389
+ const sanMethName = methodName.trim();
1390
+ const sanReturnType = (returnType !== null && returnType !== void 0 ? returnType : "").trim();
1391
+ console.error(`Tool call: create_method: Name='${sanMethName}', Return='${sanReturnType}', ParentPOU='${sanParentPath}', Project='${absPath}'`);
1392
+ if (!sanMethName) {
1393
+ // **** RETURN MCP STRUCTURE ****
1394
+ return { content: [{ type: "text", text: `Error: Method name cannot be empty.` }], isError: true };
1395
+ }
1396
+ try {
1397
+ const escProjPath = absPath.replace(/\\/g, '\\\\');
1398
+ // *** DEFINE script ***
1399
+ let script = CREATE_METHOD_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1400
+ script = script.replace("{PARENT_POU_FULL_PATH}", sanParentPath);
1401
+ script = script.replace("{METHOD_NAME}", sanMethName);
1402
+ script = script.replace("{RETURN_TYPE}", sanReturnType);
1403
+ // *** END DEFINE script ***
1404
+ console.error(">>> create_method: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1405
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1406
+ console.error(">>> create_method: EXECUTION RESULT:", JSON.stringify(result));
1407
+ const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1408
+ // **** RETURN MCP STRUCTURE ****
1409
+ return {
1410
+ content: [{ type: "text", text: success ? `Method '${sanMethName}' created under '${sanParentPath}' in ${absPath}. Project saved.` : `Failed to create method '${sanMethName}'. Output:\n${result.output}` }],
1411
+ isError: !success
1412
+ };
1413
+ }
1414
+ catch (e) {
1415
+ console.error(`Error create_method ${sanMethName} in ${absPath}: ${e}`);
1416
+ // **** RETURN MCP STRUCTURE ****
1417
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1418
+ }
1419
+ }));
1420
+ server.tool("compile_project", { projectFilePath: zod_1.z.string().describe("Path to project.") }, (args) => __awaiter(this, void 0, void 0, function* () {
1421
+ const { projectFilePath } = args;
1422
+ let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1423
+ console.error(`Tool call: compile_project: ${absPath}`);
1424
+ try {
1425
+ const escapedPath = absPath.replace(/\\/g, '\\\\');
1426
+ // *** DEFINE script ***
1427
+ const script = COMPILE_PROJECT_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPath);
1428
+ // *** END DEFINE script ***
1429
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
1430
+ const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1431
+ const hasCompileErrors = result.output.includes("Compile complete --") && !/ 0 error\(s\),/.test(result.output);
1432
+ let message = success ? `Compilation initiated for application in ${absPath}. Check CODESYS messages for results.` : `Failed initiating compilation for ${absPath}. Output:\n${result.output}`;
1433
+ let isError = !success; // Base error status on script success
1434
+ if (success && hasCompileErrors) {
1435
+ message += " WARNING: Build command reported errors in the output log.";
1436
+ console.warn("Compile project reported build errors in the output.");
1437
+ // Optionally set isError = true here if compile errors should fail the tool call
1438
+ isError = true;
1439
+ }
1440
+ // **** RETURN MCP STRUCTURE ****
1441
+ return { content: [{ type: "text", text: message }], isError: isError };
1442
+ }
1443
+ catch (e) {
1444
+ console.error(`Error compile_project ${absPath}: ${e}`);
1445
+ // **** RETURN MCP STRUCTURE ****
1446
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
1447
+ }
1448
+ }));
1449
+ // --- End Tools ---
1450
+ console.error("SERVER.TS: Resources and Tools defined.");
1451
+ // --- End MCP Resources / Tools Definitions ---
1452
+ // --- Server Connection ---
1453
+ console.error("SERVER.TS: startServer() internal logic executing.");
1428
1454
  try {
1429
1455
  const transport = new stdio_js_1.StdioServerTransport();
1430
- console.error("Connecting MCP server via stdio...");
1431
- yield server.connect(transport);
1432
- console.error("MCP Server connected and listening.");
1456
+ console.error("SERVER.TS: Connecting MCP server via stdio...");
1457
+ // No need to await connect here if startMcpServer is called by bin.ts which awaits it
1458
+ // await server.connect(transport);
1459
+ server.connect(transport); // Connect but don't await here, let bin.ts handle waiting
1460
+ console.error("SERVER.TS: MCP Server connection initiated via stdio.");
1461
+ // console.error("SERVER.TS: server.connect() promise resolved successfully."); // This log might be premature now
1433
1462
  }
1434
1463
  catch (error) {
1435
- console.error("FATAL: Failed to start MCP server:", error);
1436
- process.exit(1);
1464
+ console.error("FATAL: Failed to initiate MCP server connection:", error);
1465
+ // Re-throw error so bin.ts can catch it
1466
+ throw error;
1467
+ // process.exit(1);
1437
1468
  }
1469
+ // --- End Server Connection ---
1438
1470
  });
1439
- }
1440
- // Ensure startup happens only once if run directly
1441
- if (require.main === module) {
1442
- startServer();
1443
- }
1444
- // --- Graceful Shutdown ---
1471
+ } // --- End of startMcpServer function ---
1472
+ // --- Graceful Shutdown / Unhandled Rejection ---
1473
+ // These should remain at the top level, outside startMcpServer
1445
1474
  process.on('SIGINT', () => {
1446
- console.error('\nSIGINT received, shutting down...');
1447
- // No server.disconnect() needed for stdio
1448
- process.exit(0); // Exit the process, closing stdio streams
1475
+ console.error('\nSERVER.TS: SIGINT received, shutting down...');
1476
+ process.exit(0);
1449
1477
  });
1450
1478
  process.on('SIGTERM', () => {
1451
- console.error('\nSIGTERM received, shutting down...');
1452
- // No server.disconnect() needed for stdio
1453
- process.exit(0); // Exit the process, closing stdio streams
1479
+ console.error('\nSERVER.TS: SIGTERM received, shutting down...');
1480
+ process.exit(0);
1454
1481
  });
1455
- // Optional: Handle unhandled rejections
1456
1482
  process.on('unhandledRejection', (reason, promise) => {
1457
- console.error('Unhandled Rejection at:', promise, 'reason:', reason);
1458
- // Application specific logging, throwing an error, or other logic here
1483
+ console.error('SERVER.TS: Unhandled Rejection at:', promise, 'reason:', reason);
1459
1484
  });
1485
+ // --- End Graceful Shutdown / Unhandled Rejection ---
1486
+ console.error(">>> SERVER.TS Module Parsed <<<"); // Log end of script parsing