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.
- package/LICENSE +21 -0
- package/README.md +198 -0
- package/dist/core/envelope.js +8 -0
- package/dist/core/errors.js +12 -0
- package/dist/core/http.js +19 -0
- package/dist/core/process.js +30 -0
- package/dist/core/response.js +37 -0
- package/dist/core/scratch.js +6 -0
- package/dist/core/staticServer.js +88 -0
- package/dist/server.js +230 -0
- package/dist/state.js +57 -0
- package/dist/tools/applyProjectChanges.js +371 -0
- package/dist/tools/deployAll.js +320 -0
- package/dist/tools/hmiBrowser.js +224 -0
- package/dist/tools/hmiRuntime.js +273 -0
- package/dist/tools/inspectProject.js +162 -0
- package/dist/tools/inspectVisuProject.js +474 -0
- package/dist/tools/larsRuntime.js +536 -0
- package/dist/tools/lasalApps.js +111 -0
- package/dist/tools/plcControl.js +530 -0
- package/dist/tools/plcDiagnostics.js +172 -0
- package/dist/tools/readClassSource.js +147 -0
- package/dist/tools/selectProject.js +47 -0
- package/dist/tools/setTargetIp.js +114 -0
- package/dist/tools/status.js +146 -0
- package/dist/tools/visuControl.js +447 -0
- package/dist/tools/visuDashboard.js +571 -0
- package/dist/utils/batchScript.js +257 -0
- package/dist/utils/config.js +34 -0
- package/dist/utils/editTransaction.js +39 -0
- package/dist/utils/engine.js +163 -0
- package/dist/utils/lars.js +471 -0
- package/dist/utils/lasalXml.js +758 -0
- package/dist/utils/preflight.js +194 -0
- package/dist/utils/projectScanner.js +212 -0
- package/dist/utils/resolvePaths.js +46 -0
- package/dist/utils/respond.js +14 -0
- package/dist/utils/scriptRunner.js +161 -0
- package/dist/utils/visuDashboardIO.js +198 -0
- package/dist/utils/visuPropertyEncoding.js +174 -0
- package/dist/utils/visuScript.js +262 -0
- package/package.json +64 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, createReadStream } from "fs";
|
|
2
|
+
import { join, dirname, basename } from "path";
|
|
3
|
+
import { createInterface } from "readline";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { resolveLvpPath } from "../utils/resolvePaths.js";
|
|
6
|
+
export const inspectVisuProjectSchema = {
|
|
7
|
+
lvp_path: z
|
|
8
|
+
.string()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe("Full path to the .lvp file. Omit to auto-detect from the selected project."),
|
|
11
|
+
scope: z
|
|
12
|
+
.enum(["summary", "dashboards", "function_blocks", "code_modules", "datapoints", "dashboard_detail"])
|
|
13
|
+
.optional()
|
|
14
|
+
.default("summary")
|
|
15
|
+
.describe("Detail scope to inspect. Omit or set to 'summary' for the default summary."),
|
|
16
|
+
filter: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Filter/query string (regex) for datapoint scan, or dashboard name/filename for dashboard_detail scope. Required when scope='datapoints' or scope='dashboard_detail'."),
|
|
20
|
+
limit: z
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.optional()
|
|
24
|
+
.default(50)
|
|
25
|
+
.describe("Maximum matches to return when scope='datapoints'. Default 50."),
|
|
26
|
+
};
|
|
27
|
+
function readJson(filePath) {
|
|
28
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
29
|
+
}
|
|
30
|
+
async function scanDatapointsStream(filePath, filterRegex, limit) {
|
|
31
|
+
const fileStream = createReadStream(filePath, { encoding: "utf-8" });
|
|
32
|
+
const rl = createInterface({
|
|
33
|
+
input: fileStream,
|
|
34
|
+
crlfDelay: Infinity,
|
|
35
|
+
});
|
|
36
|
+
const results = [];
|
|
37
|
+
const nameStack = [];
|
|
38
|
+
let currentName = null;
|
|
39
|
+
let currentDatatype = null;
|
|
40
|
+
let inDatatype = false;
|
|
41
|
+
for await (const line of rl) {
|
|
42
|
+
if (line.includes('"children": [')) {
|
|
43
|
+
if (currentName) {
|
|
44
|
+
nameStack.push(currentName);
|
|
45
|
+
currentName = null;
|
|
46
|
+
currentDatatype = null;
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (line.trim().startsWith("]")) {
|
|
51
|
+
if (nameStack.length > 0) {
|
|
52
|
+
nameStack.pop();
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const nameMatch = line.match(/"name"\s*:\s*"([^"]+)"/);
|
|
57
|
+
if (nameMatch?.[1]) {
|
|
58
|
+
currentName = nameMatch[1];
|
|
59
|
+
}
|
|
60
|
+
if (line.includes('"datatype"')) {
|
|
61
|
+
inDatatype = true;
|
|
62
|
+
const valMatch = line.match(/"value"\s*:\s*"([^"]+)"/);
|
|
63
|
+
if (valMatch?.[1]) {
|
|
64
|
+
currentDatatype = valMatch[1];
|
|
65
|
+
inDatatype = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (inDatatype) {
|
|
69
|
+
const valMatch = line.match(/"value"\s*:\s*"([^"]+)"/);
|
|
70
|
+
if (valMatch?.[1]) {
|
|
71
|
+
currentDatatype = valMatch[1];
|
|
72
|
+
inDatatype = false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (line.trim().startsWith("}") || line.trim().startsWith("},")) {
|
|
76
|
+
if (currentName) {
|
|
77
|
+
const fullPath = [...nameStack, currentName].join(".");
|
|
78
|
+
if (filterRegex.test(fullPath)) {
|
|
79
|
+
results.push({ path: fullPath, datatype: currentDatatype ?? "unknown" });
|
|
80
|
+
if (results.length >= limit) {
|
|
81
|
+
rl.close();
|
|
82
|
+
fileStream.destroy();
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
currentName = null;
|
|
88
|
+
currentDatatype = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
async function getDatapointSummary(filePath) {
|
|
94
|
+
const fileStream = createReadStream(filePath, { encoding: "utf-8" });
|
|
95
|
+
const rl = createInterface({
|
|
96
|
+
input: fileStream,
|
|
97
|
+
crlfDelay: Infinity,
|
|
98
|
+
});
|
|
99
|
+
const roots = [];
|
|
100
|
+
let total = 0;
|
|
101
|
+
const nameStack = [];
|
|
102
|
+
let currentName = null;
|
|
103
|
+
let currentDatatype = null;
|
|
104
|
+
let inDatatype = false;
|
|
105
|
+
for await (const line of rl) {
|
|
106
|
+
if (line.includes('"children": [')) {
|
|
107
|
+
if (currentName) {
|
|
108
|
+
nameStack.push(currentName);
|
|
109
|
+
currentName = null;
|
|
110
|
+
currentDatatype = null;
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (line.trim().startsWith("]")) {
|
|
115
|
+
if (nameStack.length > 0) {
|
|
116
|
+
nameStack.pop();
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const nameMatch = line.match(/"name"\s*:\s*"([^"]+)"/);
|
|
121
|
+
if (nameMatch?.[1]) {
|
|
122
|
+
currentName = nameMatch[1];
|
|
123
|
+
}
|
|
124
|
+
if (line.includes('"datatype"')) {
|
|
125
|
+
inDatatype = true;
|
|
126
|
+
const valMatch = line.match(/"value"\s*:\s*"([^"]+)"/);
|
|
127
|
+
if (valMatch?.[1]) {
|
|
128
|
+
currentDatatype = valMatch[1];
|
|
129
|
+
inDatatype = false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (inDatatype) {
|
|
133
|
+
const valMatch = line.match(/"value"\s*:\s*"([^"]+)"/);
|
|
134
|
+
if (valMatch?.[1]) {
|
|
135
|
+
currentDatatype = valMatch[1];
|
|
136
|
+
inDatatype = false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (line.trim().startsWith("}") || line.trim().startsWith("},")) {
|
|
140
|
+
if (currentName) {
|
|
141
|
+
total++;
|
|
142
|
+
if (nameStack.length === 0) {
|
|
143
|
+
roots.push({ name: currentName, datatype: currentDatatype ?? "unknown" });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
currentName = null;
|
|
147
|
+
currentDatatype = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { roots, total };
|
|
151
|
+
}
|
|
152
|
+
export async function inspectVisuProjectHandler(args) {
|
|
153
|
+
const resolved = resolveLvpPath(args.lvp_path);
|
|
154
|
+
if ("error" in resolved) {
|
|
155
|
+
return { content: [{ type: "text", text: resolved.error }], isError: true };
|
|
156
|
+
}
|
|
157
|
+
const lvpPath = resolved.path;
|
|
158
|
+
const projectDir = dirname(lvpPath);
|
|
159
|
+
const scope = args.scope ?? "summary";
|
|
160
|
+
const result = {
|
|
161
|
+
lvpPath,
|
|
162
|
+
projectName: basename(lvpPath, ".lvp"),
|
|
163
|
+
scope,
|
|
164
|
+
};
|
|
165
|
+
const errors = [];
|
|
166
|
+
// ── 1. Datapoints scope (optimized stream scan) ────────────────────────────
|
|
167
|
+
if (scope === "datapoints") {
|
|
168
|
+
if (!args.filter) {
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: "text", text: "filter is required when scope='datapoints'" }],
|
|
171
|
+
isError: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const dpFile = join(projectDir, "Datapoints", "0_Datapoints.json");
|
|
175
|
+
if (existsSync(dpFile)) {
|
|
176
|
+
try {
|
|
177
|
+
const matches = await scanDatapointsStream(dpFile, new RegExp(args.filter, "i"), args.limit ?? 50);
|
|
178
|
+
result.datapoints = matches;
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
errors.push(`datapoints scan error: ${e.message}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
result.datapoints = [];
|
|
186
|
+
}
|
|
187
|
+
if (errors.length)
|
|
188
|
+
result.errors = errors;
|
|
189
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
190
|
+
}
|
|
191
|
+
// ── 1.5. Dashboard Detail scope ──────────────────────────────────────────
|
|
192
|
+
if (scope === "dashboard_detail") {
|
|
193
|
+
if (!args.filter) {
|
|
194
|
+
return {
|
|
195
|
+
content: [
|
|
196
|
+
{ type: "text", text: "filter (dashboard name/filename) is required when scope='dashboard_detail'" },
|
|
197
|
+
],
|
|
198
|
+
isError: true,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
let foundPath = null;
|
|
202
|
+
for (const dirName of ["Dashboards", "GlobalDashboards", "Window", "ControlTemplate"]) {
|
|
203
|
+
const dir = join(projectDir, dirName);
|
|
204
|
+
if (!existsSync(dir))
|
|
205
|
+
continue;
|
|
206
|
+
try {
|
|
207
|
+
for (const f of readdirSync(dir)) {
|
|
208
|
+
if (!f.endsWith(".json"))
|
|
209
|
+
continue;
|
|
210
|
+
const name = basename(f, ".json");
|
|
211
|
+
if (name.toLowerCase() === args.filter.toLowerCase()) {
|
|
212
|
+
foundPath = join(dir, f);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch { }
|
|
218
|
+
if (foundPath)
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
if (!foundPath) {
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: "text", text: `Dashboard/window/template with name '${args.filter}' not found` }],
|
|
224
|
+
isError: true,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const data = readJson(foundPath);
|
|
229
|
+
result.dashboard = {
|
|
230
|
+
name: data.name,
|
|
231
|
+
type: data.type,
|
|
232
|
+
controlId: data.controlId,
|
|
233
|
+
designTimeId: data.designTimeId,
|
|
234
|
+
instanceId: data.instanceId,
|
|
235
|
+
properties: data.properties ?? [],
|
|
236
|
+
elements: (data.dashboardelements ?? []).map((el) => ({
|
|
237
|
+
name: el.name,
|
|
238
|
+
controlId: el.controlId,
|
|
239
|
+
designTimeId: el.designTimeId,
|
|
240
|
+
instanceId: el.instanceId,
|
|
241
|
+
properties: el.properties ?? [],
|
|
242
|
+
})),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
errors.push(`Error reading dashboard: ${e.message}`);
|
|
247
|
+
}
|
|
248
|
+
if (errors.length)
|
|
249
|
+
result.errors = errors;
|
|
250
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
251
|
+
}
|
|
252
|
+
// ── 2. Dashboards scope (element counts) ───────────────────────────────────
|
|
253
|
+
if (scope === "dashboards") {
|
|
254
|
+
const list = [];
|
|
255
|
+
for (const dirName of ["Dashboards", "GlobalDashboards", "Window"]) {
|
|
256
|
+
const dir = join(projectDir, dirName);
|
|
257
|
+
if (!existsSync(dir))
|
|
258
|
+
continue;
|
|
259
|
+
try {
|
|
260
|
+
for (const f of readdirSync(dir)) {
|
|
261
|
+
if (!f.endsWith(".json"))
|
|
262
|
+
continue;
|
|
263
|
+
const fullPath = join(dir, f);
|
|
264
|
+
try {
|
|
265
|
+
const data = readJson(fullPath);
|
|
266
|
+
const count = Array.isArray(data.dashboardelements) ? data.dashboardelements.length : 0;
|
|
267
|
+
list.push({
|
|
268
|
+
name: data.name ?? basename(f, ".json"),
|
|
269
|
+
path: fullPath,
|
|
270
|
+
elementCount: count,
|
|
271
|
+
type: dirName.slice(0, -1).toLowerCase(), // dashboard, globaldashboard, window
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
catch { }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
errors.push(`${dirName}: ${e.message}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
result.dashboards = list;
|
|
282
|
+
if (errors.length)
|
|
283
|
+
result.errors = errors;
|
|
284
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
285
|
+
}
|
|
286
|
+
// ── 3. Function Blocks scope ───────────────────────────────────────────────
|
|
287
|
+
if (scope === "function_blocks") {
|
|
288
|
+
const fbs = [];
|
|
289
|
+
const fbDir = join(projectDir, "Functionblocks");
|
|
290
|
+
if (existsSync(fbDir)) {
|
|
291
|
+
try {
|
|
292
|
+
for (const f of readdirSync(fbDir)) {
|
|
293
|
+
if (!f.endsWith(".json"))
|
|
294
|
+
continue;
|
|
295
|
+
const jsonPath = join(fbDir, f);
|
|
296
|
+
try {
|
|
297
|
+
const data = readJson(jsonPath);
|
|
298
|
+
fbs.push({
|
|
299
|
+
name: data.name ?? basename(f, ".json"),
|
|
300
|
+
jsonPath,
|
|
301
|
+
jsPath: jsonPath.replace(".json", ".js"),
|
|
302
|
+
properties: data.properties ?? [],
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
catch { }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
errors.push(`function_blocks: ${e.message}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
result.functionBlocks = fbs;
|
|
313
|
+
if (errors.length)
|
|
314
|
+
result.errors = errors;
|
|
315
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
316
|
+
}
|
|
317
|
+
// ── 4. Code Modules scope ──────────────────────────────────────────────────
|
|
318
|
+
if (scope === "code_modules") {
|
|
319
|
+
const codeModulesDir = join(projectDir, "CodeModules");
|
|
320
|
+
const list = [];
|
|
321
|
+
if (existsSync(codeModulesDir)) {
|
|
322
|
+
try {
|
|
323
|
+
const modules = readdirSync(codeModulesDir).filter((f) => f.endsWith(".js") || f.endsWith(".ts"));
|
|
324
|
+
list.push(...modules);
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
errors.push(`codeModules: ${e.message}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
result.codeModules = list;
|
|
331
|
+
if (errors.length)
|
|
332
|
+
result.errors = errors;
|
|
333
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
334
|
+
}
|
|
335
|
+
// ── 5. Default Summary scope ───────────────────────────────────────────────
|
|
336
|
+
// Stations
|
|
337
|
+
const stationsFile = join(projectDir, "Stations", "Stations.json");
|
|
338
|
+
if (existsSync(stationsFile)) {
|
|
339
|
+
try {
|
|
340
|
+
const data = readJson(stationsFile);
|
|
341
|
+
result.stations = (data.stations ?? []).map((s) => ({
|
|
342
|
+
name: s.name,
|
|
343
|
+
connection: s.connection,
|
|
344
|
+
importFilePath: s.importFilePath,
|
|
345
|
+
isRequired: s.isRequired,
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
errors.push(`stations: ${e.message}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Languages
|
|
353
|
+
const locDir = join(projectDir, "Localization");
|
|
354
|
+
if (existsSync(locDir)) {
|
|
355
|
+
try {
|
|
356
|
+
result.languages = readdirSync(locDir).filter((d) => {
|
|
357
|
+
try {
|
|
358
|
+
return readdirSync(join(locDir, d)).length > 0;
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
errors.push(`languages: ${e.message}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Datapoints summary (optimized stream)
|
|
370
|
+
const dpFile = join(projectDir, "Datapoints", "0_Datapoints.json");
|
|
371
|
+
if (existsSync(dpFile)) {
|
|
372
|
+
try {
|
|
373
|
+
const summary = await getDatapointSummary(dpFile);
|
|
374
|
+
result.datapointRoots = summary.roots;
|
|
375
|
+
result.totalDatapoints = summary.total;
|
|
376
|
+
}
|
|
377
|
+
catch (e) {
|
|
378
|
+
errors.push(`datapoints summary: ${e.message}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// Text Lists
|
|
382
|
+
const textListsDir = join(projectDir, "Localization");
|
|
383
|
+
if (existsSync(textListsDir)) {
|
|
384
|
+
try {
|
|
385
|
+
const textLists = [];
|
|
386
|
+
for (const langDir of readdirSync(textListsDir)) {
|
|
387
|
+
const langPath = join(textListsDir, langDir);
|
|
388
|
+
try {
|
|
389
|
+
for (const f of readdirSync(langPath)) {
|
|
390
|
+
if (!f.endsWith(".json"))
|
|
391
|
+
continue;
|
|
392
|
+
try {
|
|
393
|
+
const data = readJson(join(langPath, f));
|
|
394
|
+
const name = data.name ?? basename(f, ".json");
|
|
395
|
+
if (!textLists.includes(name))
|
|
396
|
+
textLists.push(name);
|
|
397
|
+
}
|
|
398
|
+
catch { }
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch { }
|
|
402
|
+
}
|
|
403
|
+
if (textLists.length)
|
|
404
|
+
result.textLists = textLists;
|
|
405
|
+
}
|
|
406
|
+
catch (e) {
|
|
407
|
+
errors.push(`textLists: ${e.message}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Schemes
|
|
411
|
+
const schemesDir = join(projectDir, "Schemes");
|
|
412
|
+
if (existsSync(schemesDir)) {
|
|
413
|
+
try {
|
|
414
|
+
const schemes = [];
|
|
415
|
+
for (const typeDir of readdirSync(schemesDir)) {
|
|
416
|
+
const typeFullPath = join(schemesDir, typeDir);
|
|
417
|
+
try {
|
|
418
|
+
for (const schemeFile of readdirSync(typeFullPath)) {
|
|
419
|
+
if (!schemeFile.endsWith(".json"))
|
|
420
|
+
continue;
|
|
421
|
+
try {
|
|
422
|
+
const data = readJson(join(typeFullPath, schemeFile));
|
|
423
|
+
if (data && Array.isArray(data.schemes)) {
|
|
424
|
+
for (const s of data.schemes) {
|
|
425
|
+
if (s && s.name) {
|
|
426
|
+
schemes.push({
|
|
427
|
+
type: typeDir,
|
|
428
|
+
name: s.name,
|
|
429
|
+
designTimeId: s.designTimeId,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch { }
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch { }
|
|
439
|
+
}
|
|
440
|
+
if (schemes.length)
|
|
441
|
+
result.schemes = schemes;
|
|
442
|
+
}
|
|
443
|
+
catch (e) {
|
|
444
|
+
errors.push(`schemes: ${e.message}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Alarms
|
|
448
|
+
const alarmsDir = join(projectDir, "Alarms");
|
|
449
|
+
if (existsSync(alarmsDir)) {
|
|
450
|
+
try {
|
|
451
|
+
const alarms = [];
|
|
452
|
+
for (const f of readdirSync(alarmsDir)) {
|
|
453
|
+
if (!f.endsWith(".json"))
|
|
454
|
+
continue;
|
|
455
|
+
try {
|
|
456
|
+
const data = readJson(join(alarmsDir, f));
|
|
457
|
+
const name = data.name ?? basename(f, ".json");
|
|
458
|
+
alarms.push(name);
|
|
459
|
+
}
|
|
460
|
+
catch { }
|
|
461
|
+
}
|
|
462
|
+
if (alarms.length)
|
|
463
|
+
result.alarms = alarms;
|
|
464
|
+
}
|
|
465
|
+
catch (e) {
|
|
466
|
+
errors.push(`alarms: ${e.message}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (errors.length)
|
|
470
|
+
result.errors = errors;
|
|
471
|
+
return {
|
|
472
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
473
|
+
};
|
|
474
|
+
}
|