lasal-mcp 0.1.0

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +198 -0
  3. package/dist/core/envelope.js +8 -0
  4. package/dist/core/errors.js +12 -0
  5. package/dist/core/http.js +19 -0
  6. package/dist/core/process.js +30 -0
  7. package/dist/core/response.js +37 -0
  8. package/dist/core/scratch.js +6 -0
  9. package/dist/core/staticServer.js +88 -0
  10. package/dist/server.js +230 -0
  11. package/dist/state.js +57 -0
  12. package/dist/tools/applyProjectChanges.js +371 -0
  13. package/dist/tools/deployAll.js +320 -0
  14. package/dist/tools/hmiBrowser.js +224 -0
  15. package/dist/tools/hmiRuntime.js +273 -0
  16. package/dist/tools/inspectProject.js +162 -0
  17. package/dist/tools/inspectVisuProject.js +474 -0
  18. package/dist/tools/larsRuntime.js +536 -0
  19. package/dist/tools/lasalApps.js +111 -0
  20. package/dist/tools/plcControl.js +530 -0
  21. package/dist/tools/plcDiagnostics.js +172 -0
  22. package/dist/tools/readClassSource.js +147 -0
  23. package/dist/tools/selectProject.js +47 -0
  24. package/dist/tools/setTargetIp.js +114 -0
  25. package/dist/tools/status.js +146 -0
  26. package/dist/tools/visuControl.js +447 -0
  27. package/dist/tools/visuDashboard.js +571 -0
  28. package/dist/utils/batchScript.js +257 -0
  29. package/dist/utils/config.js +34 -0
  30. package/dist/utils/editTransaction.js +39 -0
  31. package/dist/utils/engine.js +163 -0
  32. package/dist/utils/lars.js +471 -0
  33. package/dist/utils/lasalXml.js +758 -0
  34. package/dist/utils/preflight.js +194 -0
  35. package/dist/utils/projectScanner.js +212 -0
  36. package/dist/utils/resolvePaths.js +46 -0
  37. package/dist/utils/respond.js +14 -0
  38. package/dist/utils/scriptRunner.js +161 -0
  39. package/dist/utils/visuDashboardIO.js +198 -0
  40. package/dist/utils/visuPropertyEncoding.js +174 -0
  41. package/dist/utils/visuScript.js +262 -0
  42. package/package.json +64 -0
package/dist/state.js ADDED
@@ -0,0 +1,57 @@
1
+ import { readFileSync, writeFileSync, existsSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ const STATE_FILE = join(dirname(fileURLToPath(import.meta.url)), "..", "lasal-state.json");
5
+ const DEFAULT_STATE = {
6
+ currentProject: null,
7
+ };
8
+ export function readState() {
9
+ if (!existsSync(STATE_FILE))
10
+ return { ...DEFAULT_STATE };
11
+ try {
12
+ return JSON.parse(readFileSync(STATE_FILE, "utf-8"));
13
+ }
14
+ catch {
15
+ return { ...DEFAULT_STATE };
16
+ }
17
+ }
18
+ export function writeState(state) {
19
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), "utf-8");
20
+ }
21
+ export function getHmiForProject(state, projectDir) {
22
+ const key = projectDir ?? state.currentProject;
23
+ if (!key)
24
+ return state.hmiRuntime;
25
+ return state.hmiRuntimes?.[key] ?? state.hmiRuntime;
26
+ }
27
+ export function setHmiForProject(state, info, projectDir) {
28
+ const key = projectDir ?? state.currentProject;
29
+ state.hmiRuntime = info;
30
+ if (key) {
31
+ if (!state.hmiRuntimes)
32
+ state.hmiRuntimes = {};
33
+ state.hmiRuntimes[key] = info;
34
+ }
35
+ }
36
+ export function clearHmiForProject(state, projectDir) {
37
+ const key = projectDir ?? state.currentProject;
38
+ state.hmiRuntime = undefined;
39
+ if (key && state.hmiRuntimes) {
40
+ delete state.hmiRuntimes[key];
41
+ }
42
+ }
43
+ export function getLarsInstance(state, name) {
44
+ return state.larsInstances?.[name];
45
+ }
46
+ export function setLarsInstance(state, info) {
47
+ if (!state.larsInstances)
48
+ state.larsInstances = {};
49
+ state.larsInstances[info.name] = info;
50
+ }
51
+ export function removeLarsInstance(state, name) {
52
+ if (state.larsInstances) {
53
+ delete state.larsInstances[name];
54
+ if (Object.keys(state.larsInstances).length === 0)
55
+ state.larsInstances = undefined;
56
+ }
57
+ }
@@ -0,0 +1,371 @@
1
+ import { z } from "zod";
2
+ import { runBatchOps } from "../utils/batchScript.js";
3
+ import { resolveLcpPath } from "../utils/resolvePaths.js";
4
+ import { withEngineLock, killClass2, killVisuDesigner } from "../utils/engine.js";
5
+ import { respond, fail } from "../utils/respond.js";
6
+ // ─── Batch operation schemas (all require CLASS 2 engine) ────────────────────
7
+ const CreateNetworkOp = z.object({
8
+ type: z.literal("create_network"),
9
+ name: z.string(),
10
+ });
11
+ const DeleteNetworkOp = z.object({
12
+ type: z.literal("delete_network"),
13
+ name: z.string(),
14
+ deleteConnections: z.boolean().optional().default(true),
15
+ });
16
+ const RenameNetworkOp = z.object({
17
+ type: z.literal("rename_network"),
18
+ oldName: z.string(),
19
+ newName: z.string(),
20
+ });
21
+ const DuplicateNetworkOp = z.object({
22
+ type: z.literal("duplicate_network"),
23
+ name: z.string(),
24
+ newName: z.string(),
25
+ });
26
+ const AddObjectOp = z.object({
27
+ type: z.literal("add_object"),
28
+ network: z.string(),
29
+ className: z.string(),
30
+ objectName: z.string(),
31
+ x: z.number().optional().default(300),
32
+ y: z.number().optional().default(300),
33
+ visualized: z.boolean().optional().default(true),
34
+ });
35
+ const RemoveObjectOp = z.object({
36
+ type: z.literal("remove_object"),
37
+ network: z.string(),
38
+ objectName: z.string(),
39
+ deleteConnections: z.boolean().optional().default(true),
40
+ });
41
+ const RenameObjectOp = z.object({
42
+ type: z.literal("rename_object"),
43
+ network: z.string(),
44
+ oldName: z.string(),
45
+ newName: z.string(),
46
+ });
47
+ const ChangeObjectClassOp = z.object({
48
+ type: z.literal("change_object_class"),
49
+ network: z.string(),
50
+ objectName: z.string(),
51
+ className: z.string(),
52
+ });
53
+ const CreateConnectionOp = z.object({
54
+ type: z.literal("create_connection"),
55
+ network: z.string().optional(),
56
+ fromObject: z.string(),
57
+ fromClient: z.string(),
58
+ toObject: z.string(),
59
+ toServer: z.string(),
60
+ });
61
+ const DeleteConnectionOp = z.object({
62
+ type: z.literal("delete_connection"),
63
+ network: z.string().optional(),
64
+ objectName: z.string(),
65
+ clientName: z.string(),
66
+ });
67
+ const SetInitValueOp = z.object({
68
+ type: z.literal("set_init_value"),
69
+ network: z.string().optional(),
70
+ objectName: z.string(),
71
+ channelName: z.string(),
72
+ value: z.string(),
73
+ });
74
+ const DeleteClassOp = z.object({
75
+ type: z.literal("delete_class"),
76
+ className: z.string(),
77
+ force: z.boolean().optional().default(false),
78
+ });
79
+ const CompileOp = z.object({
80
+ type: z.literal("compile"),
81
+ options: z.enum(["RebuildAll", "BuildChanges", "UserClassesOnly", "NoDebugInfo"]).optional().default("RebuildAll"),
82
+ });
83
+ const DownloadOp = z.object({
84
+ type: z.literal("download"),
85
+ connection: z.string().optional(),
86
+ add_loader_anyway: z.boolean().optional().default(false),
87
+ });
88
+ const SetTaskOrderOp = z.object({
89
+ type: z.literal("set_task_order"),
90
+ network: z.string(),
91
+ objectName: z.string(),
92
+ task: z.enum(["realtime", "cyclicwork", "background"]),
93
+ position: z.number().int(),
94
+ });
95
+ const SetTaskTimeOp = z.object({
96
+ type: z.literal("set_task_time"),
97
+ network: z.string(),
98
+ objectName: z.string(),
99
+ task: z.enum(["realtime", "cyclicwork", "background"]),
100
+ time: z.string(),
101
+ });
102
+ const SetTaskCpuCoreOp = z.object({
103
+ type: z.literal("set_task_cpu_core"),
104
+ network: z.string(),
105
+ objectName: z.string(),
106
+ task: z.enum(["realtime", "cyclicwork"]),
107
+ core: z.number().int(),
108
+ });
109
+ const SetMultiCpuCoreOp = z.object({
110
+ type: z.literal("set_multi_cpu_core"),
111
+ multiCore: z.boolean(),
112
+ });
113
+ const SetVisualizedFlagOp = z.object({
114
+ type: z.literal("set_visualized_flag"),
115
+ network: z.string(),
116
+ objectName: z.string(),
117
+ isVisualized: z.boolean(),
118
+ });
119
+ const SetCommentNetworkOp = z.object({
120
+ type: z.literal("set_comment_network"),
121
+ network: z.string(),
122
+ comment: z.string(),
123
+ });
124
+ const SetCommentObjectOp = z.object({
125
+ type: z.literal("set_comment_object"),
126
+ network: z.string(),
127
+ objectName: z.string(),
128
+ comment: z.string(),
129
+ });
130
+ const SetNetworkOptionsOp = z.object({
131
+ type: z.literal("set_network_options"),
132
+ network: z.string(),
133
+ optionNames: z.array(z.string()),
134
+ resetAllOthers: z.boolean().optional().default(false),
135
+ });
136
+ const ResetNetworkOptionsOp = z.object({
137
+ type: z.literal("reset_network_options"),
138
+ network: z.string(),
139
+ optionNames: z.array(z.string()),
140
+ });
141
+ const MoveNetworkToFolderOp = z.object({
142
+ type: z.literal("move_network_to_folder"),
143
+ network: z.string(),
144
+ folder: z.string(),
145
+ });
146
+ const SetParameterValueOp = z.object({
147
+ type: z.literal("set_parameter_value"),
148
+ network: z.string(),
149
+ objectName: z.string(),
150
+ parameterName: z.string(),
151
+ value: z.string(),
152
+ });
153
+ const OperationSchema = z.discriminatedUnion("type", [
154
+ CreateNetworkOp,
155
+ DeleteNetworkOp,
156
+ RenameNetworkOp,
157
+ DuplicateNetworkOp,
158
+ AddObjectOp,
159
+ RemoveObjectOp,
160
+ RenameObjectOp,
161
+ ChangeObjectClassOp,
162
+ CreateConnectionOp,
163
+ DeleteConnectionOp,
164
+ SetInitValueOp,
165
+ DeleteClassOp,
166
+ CompileOp,
167
+ DownloadOp,
168
+ SetTaskOrderOp,
169
+ SetTaskTimeOp,
170
+ SetTaskCpuCoreOp,
171
+ SetMultiCpuCoreOp,
172
+ SetVisualizedFlagOp,
173
+ SetCommentNetworkOp,
174
+ SetCommentObjectOp,
175
+ SetNetworkOptionsOp,
176
+ ResetNetworkOptionsOp,
177
+ MoveNetworkToFolderOp,
178
+ SetParameterValueOp,
179
+ ]);
180
+ export const applyProjectChangesSchema = {
181
+ lcp_path: z
182
+ .string()
183
+ .optional()
184
+ .describe("Absolute path to the .lcp file. Omit to use the currently selected project."),
185
+ operations: z
186
+ .array(OperationSchema)
187
+ .describe("Ordered list of CLASS 2 batch engine operations. " +
188
+ "Available types: create_network, delete_network, rename_network, duplicate_network, " +
189
+ "add_object, remove_object, rename_object, change_object_class, " +
190
+ "create_connection, delete_connection, set_init_value, delete_class, " +
191
+ "compile, download, set_task_order, set_task_time, set_task_cpu_core, " +
192
+ "set_multi_cpu_core, set_visualized_flag, set_comment_network, set_comment_object, " +
193
+ "set_network_options, reset_network_options, move_network_to_folder, set_parameter_value"),
194
+ dry_run: z.boolean().optional().default(false).describe("Validate operations without applying them."),
195
+ };
196
+ function toBatchOp(op) {
197
+ switch (op.type) {
198
+ case "create_network":
199
+ return { type: "create_network", name: op.name };
200
+ case "delete_network":
201
+ return { type: "delete_network", name: op.name, deleteConnections: op.deleteConnections };
202
+ case "rename_network":
203
+ return { type: "rename_network", oldName: op.oldName, newName: op.newName };
204
+ case "duplicate_network":
205
+ return { type: "duplicate_network", name: op.name, newName: op.newName };
206
+ case "add_object":
207
+ return {
208
+ type: "add_object",
209
+ network: op.network,
210
+ className: op.className,
211
+ objectName: op.objectName,
212
+ x: op.x,
213
+ y: op.y,
214
+ visualized: op.visualized,
215
+ };
216
+ case "remove_object":
217
+ return {
218
+ type: "remove_object",
219
+ network: op.network,
220
+ objectName: op.objectName,
221
+ deleteConnections: op.deleteConnections,
222
+ };
223
+ case "rename_object":
224
+ return { type: "rename_object", network: op.network, oldName: op.oldName, newName: op.newName };
225
+ case "change_object_class":
226
+ return { type: "change_object_class", network: op.network, objectName: op.objectName, className: op.className };
227
+ case "create_connection":
228
+ return {
229
+ type: "create_connection",
230
+ network: op.network,
231
+ fromObject: op.fromObject,
232
+ fromClient: op.fromClient,
233
+ toObject: op.toObject,
234
+ toServer: op.toServer,
235
+ };
236
+ case "delete_connection":
237
+ return { type: "delete_connection", network: op.network, objectName: op.objectName, clientName: op.clientName };
238
+ case "set_init_value":
239
+ return {
240
+ type: "set_init_value",
241
+ network: op.network,
242
+ objectName: op.objectName,
243
+ channelName: op.channelName,
244
+ value: op.value,
245
+ };
246
+ case "delete_class":
247
+ return { type: "delete_class", className: op.className, force: op.force };
248
+ case "compile":
249
+ return { type: "compile", optionName: op.options };
250
+ case "download":
251
+ return { type: "download", connection: op.connection ?? "", addLoaderAnyway: op.add_loader_anyway };
252
+ case "set_task_order":
253
+ return {
254
+ type: "set_task_order",
255
+ network: op.network,
256
+ objectName: op.objectName,
257
+ task: op.task,
258
+ position: op.position,
259
+ };
260
+ case "set_task_time":
261
+ return { type: "set_task_time", network: op.network, objectName: op.objectName, task: op.task, time: op.time };
262
+ case "set_task_cpu_core":
263
+ return {
264
+ type: "set_task_cpu_core",
265
+ network: op.network,
266
+ objectName: op.objectName,
267
+ task: op.task,
268
+ core: op.core,
269
+ };
270
+ case "set_multi_cpu_core":
271
+ return { type: "set_multi_cpu_core", multiCore: op.multiCore };
272
+ case "set_visualized_flag":
273
+ return {
274
+ type: "set_visualized_flag",
275
+ network: op.network,
276
+ objectName: op.objectName,
277
+ isVisualized: op.isVisualized,
278
+ };
279
+ case "set_comment_network":
280
+ return { type: "set_comment_network", network: op.network, comment: op.comment };
281
+ case "set_comment_object":
282
+ return { type: "set_comment_object", network: op.network, objectName: op.objectName, comment: op.comment };
283
+ case "set_network_options":
284
+ return {
285
+ type: "set_network_options",
286
+ network: op.network,
287
+ optionNames: op.optionNames,
288
+ resetAllOthers: op.resetAllOthers,
289
+ };
290
+ case "reset_network_options":
291
+ return { type: "reset_network_options", network: op.network, optionNames: op.optionNames };
292
+ case "move_network_to_folder":
293
+ return { type: "move_network_to_folder", network: op.network, folder: op.folder };
294
+ case "set_parameter_value":
295
+ return {
296
+ type: "set_parameter_value",
297
+ network: op.network,
298
+ objectName: op.objectName,
299
+ parameterName: op.parameterName,
300
+ value: op.value,
301
+ };
302
+ }
303
+ }
304
+ export async function applyProjectChangesHandler(args) {
305
+ return withEngineLock(async () => {
306
+ const resolved = resolveLcpPath(args.lcp_path);
307
+ if ("error" in resolved) {
308
+ return fail(resolved.error, ["Select a project first using select_project or specify lcp_path."]);
309
+ }
310
+ const ops = [];
311
+ const parseErrors = [];
312
+ for (let i = 0; i < args.operations.length; i++) {
313
+ const result = OperationSchema.safeParse(args.operations[i]);
314
+ if (result.success) {
315
+ ops.push(result.data);
316
+ }
317
+ else {
318
+ parseErrors.push(`Operation[${i}]: ${result.error.message}`);
319
+ }
320
+ }
321
+ if (parseErrors.length) {
322
+ return fail(`Invalid operations:\n${parseErrors.join("\n")}`, []);
323
+ }
324
+ if (args.dry_run) {
325
+ const plan = ops.map((op, i) => ({
326
+ index: i,
327
+ type: op.type,
328
+ target: op.name ?? op.network ?? op.objectName ?? "",
329
+ }));
330
+ return respond({
331
+ ok: true,
332
+ dryRun: true,
333
+ operationCount: ops.length,
334
+ plan,
335
+ hints: ["Pass dry_run: false (or omit it) to apply these operations."],
336
+ });
337
+ }
338
+ killClass2();
339
+ killVisuDesigner();
340
+ const batchOps = ops.map(toBatchOp);
341
+ const results = ops.map((op) => ({
342
+ op: op.type,
343
+ target: op.name ?? op.objectName ?? op.network ?? "",
344
+ ok: true,
345
+ message: "Queued for batch",
346
+ }));
347
+ const br = await runBatchOps(resolved.path, batchOps);
348
+ const batchResult = {
349
+ ok: br.ok,
350
+ exitCode: br.exitCode,
351
+ durationMs: br.durationMs,
352
+ errors: br.errors,
353
+ warnings: br.warnings,
354
+ logPath: br.logPath,
355
+ };
356
+ for (const r of results) {
357
+ if (br.ok) {
358
+ r.message = "Applied via batch";
359
+ }
360
+ else {
361
+ r.ok = false;
362
+ r.message = "Batch script failed — see batchResult.errors";
363
+ }
364
+ }
365
+ return respond({
366
+ ok: br.ok,
367
+ operations: results,
368
+ batchResult,
369
+ });
370
+ });
371
+ }