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
@@ -0,0 +1,447 @@
1
+ import { z } from "zod";
2
+ import { runVisuOps } from "../utils/visuScript.js";
3
+ import { resolveLvpPath } from "../utils/resolvePaths.js";
4
+ import { withEngineLock } from "../utils/engine.js";
5
+ import { preflightHmi } from "../utils/preflight.js";
6
+ import { respond, fail } from "../utils/respond.js";
7
+ function visuResultToResponse(r, extra) {
8
+ const body = {
9
+ ok: r.ok,
10
+ durationMs: r.durationMs,
11
+ ...(r.errors.length ? { errors: r.errors } : {}),
12
+ ...(r.warnings.length ? { warnings: r.warnings } : {}),
13
+ logPath: r.logPath,
14
+ ...(r.hints?.length ? { hints: r.hints } : {}),
15
+ ...extra,
16
+ };
17
+ return respond(body);
18
+ }
19
+ // ── Operation schemas ─────────────────────────────────────────────────────────
20
+ const textEntrySchema = z
21
+ .record(z.string(), z.string())
22
+ .refine((v) => "id" in v, { message: 'Each text entry must have an "id" field' })
23
+ .describe('Text entry: { "id": "KEY", "De": "German text", "En": "English text", ... }');
24
+ const textListDefSchema = z.object({
25
+ name: z.string().describe("Text list name"),
26
+ texts: z.array(textEntrySchema).optional().describe("Text entries with translations per language"),
27
+ revision: z.string().optional().describe("Revision string, e.g. '1.0'"),
28
+ });
29
+ const textListRemoveDefSchema = z.object({
30
+ name: z.string(),
31
+ ids: z.array(z.string()).describe("IDs of texts to remove"),
32
+ });
33
+ const propertySetDefSchema = z.object({
34
+ element: z.string().describe("Datapoint / datatype / scheme name"),
35
+ property: z
36
+ .string()
37
+ .describe("Property constant, e.g. 'AliasName', 'RefreshTime', 'WriteProtected', 'LimitLow', 'LimitHigh', 'Unit', 'Keyboard', 'EnumValue', 'InitValue'"),
38
+ value: z
39
+ .union([z.string(), z.number(), z.boolean(), z.null()])
40
+ .describe("Value to set. Numbers and booleans are auto-converted to strings by the API."),
41
+ });
42
+ const schemeEntryDefSchema = z.object({
43
+ position: z.number().int().describe("0-based position in the scheme"),
44
+ property: z.string().optional().describe("Entry property to set, e.g. 'SetValue', 'CompareValue', 'Operator'"),
45
+ value: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional().describe("Value to set for the property"),
46
+ new_position: z.number().int().optional().describe("Target position for move operations"),
47
+ });
48
+ const schemeTypeDesc = "Scheme type constant: 'ColorScheme', 'StateScheme', 'FontStyleScheme', 'KeyboardScheme', 'KeyboardLayoutScheme', 'MediaScheme', 'NavigationScheme', 'TextScheme', 'UnitScheme', 'DatapointScheme', 'CompositeControlScheme'";
49
+ const schemeDefSchema = z.object({
50
+ scheme_type: z.string().describe(schemeTypeDesc),
51
+ name: z.string().describe("Scheme name"),
52
+ input: z.string().optional().describe("Input datapoint name for the scheme"),
53
+ entries: z.array(schemeEntryDefSchema).optional().describe("Scheme entries"),
54
+ positions: z.array(z.number().int()).optional().describe("Entry positions (for remove_scheme_entries)"),
55
+ moves: z
56
+ .array(z.object({ from: z.number().int(), to: z.number().int() }))
57
+ .optional()
58
+ .describe("Move operations (for move_scheme_entries)"),
59
+ });
60
+ const mediaItemDefSchema = z.object({
61
+ media_type: z.string().describe("Media type: 'Image', 'Video', 'Audio', 'Docs', 'Fonts', 'KeyboardLayouts', 'Other'"),
62
+ path: z.string().optional().describe("Full file path (for add operations)"),
63
+ name: z.string().optional().describe("File name including extension (for remove operations)"),
64
+ });
65
+ const UpdateAllStationsOp = z.object({ type: z.literal("update_all_stations") });
66
+ const UpdateStationOp = z.object({
67
+ type: z.literal("update_station"),
68
+ station_nr: z.number().int().describe("Station number to update (as defined in the VISUDesigner project)"),
69
+ });
70
+ const PublishOp = z.object({
71
+ type: z.literal("publish"),
72
+ debug: z
73
+ .boolean()
74
+ .optional()
75
+ .default(false)
76
+ .describe("Publish with additional debug information. Requires TypeScript support to be enabled in the project."),
77
+ });
78
+ const SetTextListRevisionsOp = z.object({
79
+ type: z.literal("set_text_list_revisions"),
80
+ text_lists: z
81
+ .array(textListDefSchema)
82
+ .describe("Text lists with their revision strings to set (use the 'revision' field)"),
83
+ });
84
+ const SetComponentTextListRevisionsOp = z.object({
85
+ type: z.literal("set_component_text_list_revisions"),
86
+ text_lists: z
87
+ .array(textListDefSchema)
88
+ .describe("Component text lists with their revision strings to set (use the 'revision' field)"),
89
+ });
90
+ const AddTextListsOp = z.object({
91
+ type: z.literal("add_text_lists"),
92
+ text_lists: z.array(textListDefSchema).describe("Text lists to add (optionally with texts and revisions)"),
93
+ });
94
+ const RemoveTextListsOp = z.object({
95
+ type: z.literal("remove_text_lists"),
96
+ names: z.array(z.string()).describe("Names of text lists to remove"),
97
+ });
98
+ const AddTextsOp = z.object({
99
+ type: z.literal("add_texts"),
100
+ text_lists: z.array(textListDefSchema).describe("Text lists with texts to add"),
101
+ });
102
+ const RemoveTextsOp = z.object({
103
+ type: z.literal("remove_texts"),
104
+ text_lists: z.array(textListRemoveDefSchema).describe("Text lists with text IDs to remove"),
105
+ });
106
+ const ChangeTextsOp = z.object({
107
+ type: z.literal("change_texts"),
108
+ text_lists: z.array(textListDefSchema).describe("Text lists with updated text values"),
109
+ });
110
+ const ChangeComponentTextsOp = z.object({
111
+ type: z.literal("change_component_texts"),
112
+ text_lists: z.array(textListDefSchema).describe("Component text lists with updated text values"),
113
+ });
114
+ const CsvExportTextListsOp = z.object({
115
+ type: z.literal("csv_export_text_lists"),
116
+ csv_path: z.string().describe("Destination CSV file path"),
117
+ text_lists: z.array(z.string()).optional().describe("Subset of text list names to export (omit for all)"),
118
+ languages: z.array(z.string()).optional().describe("Subset of language codes to export (omit for all)"),
119
+ });
120
+ const CsvImportTextListsOp = z.object({
121
+ type: z.literal("csv_import_text_lists"),
122
+ file_paths: z.array(z.string()).describe("CSV file paths to import from"),
123
+ text_lists: z.array(z.string()).optional().describe("Subset of text list names to import (omit for all)"),
124
+ languages: z.array(z.string()).optional().describe("Subset of language codes to import (omit for all)"),
125
+ });
126
+ const CsvExportComponentTextListsOp = z.object({
127
+ type: z.literal("csv_export_component_text_lists"),
128
+ csv_path: z.string().describe("Destination CSV file path"),
129
+ text_lists: z.array(z.string()).optional(),
130
+ languages: z.array(z.string()).optional(),
131
+ });
132
+ const CsvImportComponentTextListsOp = z.object({
133
+ type: z.literal("csv_import_component_text_lists"),
134
+ file_paths: z.array(z.string()),
135
+ text_lists: z.array(z.string()).optional(),
136
+ languages: z.array(z.string()).optional(),
137
+ });
138
+ const SetDatapointPropertiesOp = z.object({
139
+ type: z.literal("set_datapoint_properties"),
140
+ properties: z
141
+ .array(propertySetDefSchema)
142
+ .describe("List of property sets. Use property constants: AliasName, RefreshTime (High/Medium/Low/Standard), WriteProtected, AccessNumber, LimitLow, LimitHigh, Unit, Keyboard, EnumValue, InitValue, etc."),
143
+ });
144
+ const SetDataTypePropertiesOp = z.object({
145
+ type: z.literal("set_datatype_properties"),
146
+ properties: z.array(propertySetDefSchema),
147
+ });
148
+ const AddSchemesOp = z.object({
149
+ type: z.literal("add_schemes"),
150
+ schemes: z
151
+ .array(schemeDefSchema)
152
+ .describe("Schemes to add. Entries and input can be specified to set them simultaneously."),
153
+ });
154
+ const RemoveSchemesOp = z.object({
155
+ type: z.literal("remove_schemes"),
156
+ schemes: z
157
+ .array(z.object({ scheme_type: z.string().describe(schemeTypeDesc), name: z.string() }))
158
+ .describe("Schemes to remove"),
159
+ });
160
+ const AddSchemeEntriesOp = z.object({
161
+ type: z.literal("add_scheme_entries"),
162
+ schemes: z
163
+ .array(schemeDefSchema)
164
+ .describe("Schemes with entries to add. Position determines insertion point; entries beyond max are appended."),
165
+ });
166
+ const RemoveSchemeEntriesOp = z.object({
167
+ type: z.literal("remove_scheme_entries"),
168
+ schemes: z
169
+ .array(schemeDefSchema)
170
+ .describe("Schemes with positions to remove. Use 'positions' or 'entries[].position'."),
171
+ });
172
+ const MoveSchemeEntriesOp = z.object({
173
+ type: z.literal("move_scheme_entries"),
174
+ schemes: z.array(schemeDefSchema).describe("Schemes with move operations. Use 'moves': [{from: N, to: M}, ...]"),
175
+ });
176
+ const SetSchemeInputsOp = z.object({
177
+ type: z.literal("set_scheme_inputs"),
178
+ schemes: z
179
+ .array(schemeDefSchema)
180
+ .describe("Schemes with the 'input' field set to the datapoint name to use as input"),
181
+ });
182
+ const SetSchemePropertiesOp = z.object({
183
+ type: z.literal("set_scheme_properties"),
184
+ scheme_type: z.string().describe(schemeTypeDesc),
185
+ properties: z
186
+ .array(propertySetDefSchema)
187
+ .describe("PropertySets where 'element' is the scheme name. Available properties: Revision, LockOverloadCompareValue, LockOverloadSetValue"),
188
+ });
189
+ const SetSchemeEntryPropertiesOp = z.object({
190
+ type: z.literal("set_scheme_entry_properties"),
191
+ schemes: z
192
+ .array(schemeDefSchema)
193
+ .describe("Schemes with entries where each entry specifies position + property + value to set. " +
194
+ "Entry properties: Operator (<,<=,=,>=,>,<>), CompareValue, SetValue. " +
195
+ "For StateScheme SetValue: None, Active, Inactive, Invisible."),
196
+ });
197
+ const AddMediaItemsOp = z.object({
198
+ type: z.literal("add_media_items"),
199
+ items: z.array(mediaItemDefSchema).describe("Media items to add. Each needs media_type and path (full file path)."),
200
+ overwrite: z.boolean().optional().default(false).describe("Overwrite existing items with the same name"),
201
+ });
202
+ const RemoveMediaItemsOp = z.object({
203
+ type: z.literal("remove_media_items"),
204
+ items: z
205
+ .array(mediaItemDefSchema)
206
+ .describe("Media items to remove. Each needs media_type and name (filename + extension)."),
207
+ });
208
+ const AddCodeModulesOp = z.object({
209
+ type: z.literal("add_code_modules"),
210
+ paths: z.array(z.string()).describe("Full paths to code module files (.js or .ts) to add"),
211
+ });
212
+ const RemoveCodeModulesOp = z.object({
213
+ type: z.literal("remove_code_modules"),
214
+ names: z.array(z.string()).describe("File names (with extension) of code modules to remove"),
215
+ });
216
+ const UpdatePropertyValuesOp = z.object({ type: z.literal("update_property_values") });
217
+ const DownloadOp = z.object({
218
+ type: z.literal("download"),
219
+ connection: z
220
+ .string()
221
+ .describe("Connection string, e.g. 'TCPIP:192.168.1.100' or 'TCPIP:myhmi.local'. Use the 'TCPIP:' prefix for IP/DNS targets."),
222
+ flags: z
223
+ .number()
224
+ .int()
225
+ .optional()
226
+ .default(0)
227
+ .describe("Download mode: 0=normal (default), 1=changes only, 2=publish+download changes"),
228
+ add_runtime: z.boolean().optional().default(false).describe("Force runtime download even if version matches"),
229
+ });
230
+ const OperationSchema = z.discriminatedUnion("type", [
231
+ UpdateAllStationsOp,
232
+ UpdateStationOp,
233
+ PublishOp,
234
+ AddTextListsOp,
235
+ RemoveTextListsOp,
236
+ AddTextsOp,
237
+ RemoveTextsOp,
238
+ ChangeTextsOp,
239
+ ChangeComponentTextsOp,
240
+ SetTextListRevisionsOp,
241
+ SetComponentTextListRevisionsOp,
242
+ CsvExportTextListsOp,
243
+ CsvImportTextListsOp,
244
+ CsvExportComponentTextListsOp,
245
+ CsvImportComponentTextListsOp,
246
+ SetDatapointPropertiesOp,
247
+ SetDataTypePropertiesOp,
248
+ AddSchemesOp,
249
+ RemoveSchemesOp,
250
+ AddSchemeEntriesOp,
251
+ RemoveSchemeEntriesOp,
252
+ MoveSchemeEntriesOp,
253
+ SetSchemeInputsOp,
254
+ SetSchemePropertiesOp,
255
+ SetSchemeEntryPropertiesOp,
256
+ AddMediaItemsOp,
257
+ RemoveMediaItemsOp,
258
+ AddCodeModulesOp,
259
+ RemoveCodeModulesOp,
260
+ UpdatePropertyValuesOp,
261
+ DownloadOp,
262
+ ]);
263
+ function parseOperations(raw) {
264
+ const ops = [];
265
+ const errors = [];
266
+ for (let i = 0; i < raw.length; i++) {
267
+ const result = OperationSchema.safeParse(raw[i]);
268
+ if (result.success)
269
+ ops.push(result.data);
270
+ else
271
+ errors.push(`Operation[${i}]: ${result.error.message}`);
272
+ }
273
+ return errors.length ? { errors } : { ops };
274
+ }
275
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
276
+ function toVisuOp(op) {
277
+ switch (op.type) {
278
+ case "update_all_stations":
279
+ return { type: "update_all_stations" };
280
+ case "update_station":
281
+ return { type: "update_station", station_nr: op.station_nr };
282
+ case "publish":
283
+ return { type: "publish", debug: op.debug };
284
+ case "add_text_lists":
285
+ return { type: "add_text_lists", text_lists: op.text_lists };
286
+ case "remove_text_lists":
287
+ return { type: "remove_text_lists", names: op.names };
288
+ case "add_texts":
289
+ return { type: "add_texts", text_lists: op.text_lists };
290
+ case "remove_texts":
291
+ return { type: "remove_texts", text_lists: op.text_lists };
292
+ case "change_texts":
293
+ return { type: "change_texts", text_lists: op.text_lists };
294
+ case "change_component_texts":
295
+ return { type: "change_component_texts", text_lists: op.text_lists };
296
+ case "set_text_list_revisions":
297
+ return { type: "set_text_list_revisions", text_lists: op.text_lists };
298
+ case "set_component_text_list_revisions":
299
+ return { type: "set_component_text_list_revisions", text_lists: op.text_lists };
300
+ case "csv_export_text_lists":
301
+ return {
302
+ type: "csv_export_text_lists",
303
+ csv_path: op.csv_path,
304
+ text_lists: op.text_lists,
305
+ languages: op.languages,
306
+ };
307
+ case "csv_import_text_lists":
308
+ return {
309
+ type: "csv_import_text_lists",
310
+ file_paths: op.file_paths,
311
+ text_lists: op.text_lists,
312
+ languages: op.languages,
313
+ };
314
+ case "csv_export_component_text_lists":
315
+ return {
316
+ type: "csv_export_component_text_lists",
317
+ csv_path: op.csv_path,
318
+ text_lists: op.text_lists,
319
+ languages: op.languages,
320
+ };
321
+ case "csv_import_component_text_lists":
322
+ return {
323
+ type: "csv_import_component_text_lists",
324
+ file_paths: op.file_paths,
325
+ text_lists: op.text_lists,
326
+ languages: op.languages,
327
+ };
328
+ case "set_datapoint_properties":
329
+ return { type: "set_datapoint_properties", properties: op.properties };
330
+ case "set_datatype_properties":
331
+ return { type: "set_datatype_properties", properties: op.properties };
332
+ case "add_schemes":
333
+ return { type: "add_schemes", schemes: op.schemes };
334
+ case "remove_schemes":
335
+ return { type: "remove_schemes", schemes: op.schemes };
336
+ case "add_scheme_entries":
337
+ return { type: "add_scheme_entries", schemes: op.schemes };
338
+ case "remove_scheme_entries":
339
+ return { type: "remove_scheme_entries", schemes: op.schemes };
340
+ case "move_scheme_entries":
341
+ return { type: "move_scheme_entries", schemes: op.schemes };
342
+ case "set_scheme_inputs":
343
+ return { type: "set_scheme_inputs", schemes: op.schemes };
344
+ case "set_scheme_properties":
345
+ return { type: "set_scheme_properties", scheme_type: op.scheme_type, properties: op.properties };
346
+ case "set_scheme_entry_properties":
347
+ return { type: "set_scheme_entry_properties", schemes: op.schemes };
348
+ case "add_media_items":
349
+ return { type: "add_media_items", items: op.items, overwrite: op.overwrite };
350
+ case "remove_media_items":
351
+ return { type: "remove_media_items", items: op.items };
352
+ case "add_code_modules":
353
+ return { type: "add_code_modules", paths: op.paths };
354
+ case "remove_code_modules":
355
+ return { type: "remove_code_modules", names: op.names };
356
+ case "update_property_values":
357
+ return { type: "update_property_values" };
358
+ case "download":
359
+ return { type: "download", connection: op.connection, flags: op.flags, add_runtime: op.add_runtime };
360
+ default:
361
+ throw new Error(`Unknown operation type: ${op.type}`);
362
+ }
363
+ }
364
+ // ── visu_project ──────────────────────────────────────────────────────────────
365
+ export const visuProjectSchema = {
366
+ action: z
367
+ .enum(["apply_changes", "download"])
368
+ .describe("'apply_changes' loads the project, runs operations, saves, and closes. " +
369
+ "'download' pushes the project to an HMI without saving content changes."),
370
+ lvp_path: z
371
+ .string()
372
+ .optional()
373
+ .describe("Full path to the .lvp project file. Omit to auto-detect from the selected project."),
374
+ operations: z
375
+ .array(OperationSchema)
376
+ .optional()
377
+ .describe("Ordered list of VISUDesigner operations (apply_changes only). Each has a 'type' field. " +
378
+ "Available types: update_all_stations | update_station | publish | " +
379
+ "add_text_lists | remove_text_lists | add_texts | " +
380
+ "remove_texts | change_texts | change_component_texts | " +
381
+ "set_text_list_revisions | set_component_text_list_revisions | " +
382
+ "csv_export_text_lists | csv_import_text_lists | " +
383
+ "csv_export_component_text_lists | csv_import_component_text_lists | " +
384
+ "set_datapoint_properties | set_datatype_properties | add_schemes | remove_schemes | " +
385
+ "add_scheme_entries | remove_scheme_entries | move_scheme_entries | set_scheme_inputs | " +
386
+ "set_scheme_properties | set_scheme_entry_properties | add_media_items | remove_media_items | " +
387
+ "add_code_modules | remove_code_modules | update_property_values | download"),
388
+ connection: z.string().optional().describe("HMI connection string, e.g. 'TCPIP:192.168.1.100' (download only)."),
389
+ flags: z
390
+ .number()
391
+ .int()
392
+ .optional()
393
+ .default(0)
394
+ .describe("Download mode: 0=normal (default), 1=changes only, 2=publish+download changes (download only)."),
395
+ add_runtime: z
396
+ .boolean()
397
+ .optional()
398
+ .default(false)
399
+ .describe("Force runtime download even if version already matches (download only)."),
400
+ };
401
+ export async function visuProjectHandler(args) {
402
+ return withEngineLock(async () => {
403
+ const resolved = resolveLvpPath(args.lvp_path);
404
+ if ("error" in resolved) {
405
+ return { content: [{ type: "text", text: resolved.error }], isError: true };
406
+ }
407
+ if (args.action === "download") {
408
+ if (!args.connection) {
409
+ return fail("connection is required for action 'download'", ["Provide the connection parameter."]);
410
+ }
411
+ const m = args.connection.match(/TCPIP:(.+)/i);
412
+ const ipUsed = m?.[1]?.split(":")[0] ?? args.connection;
413
+ const pf = await preflightHmi(resolved.path, args.connection);
414
+ if (!pf.ok) {
415
+ return respond({
416
+ ok: false,
417
+ preflight: pf,
418
+ connectionUsed: args.connection,
419
+ ipUsed,
420
+ errors: pf.problems.map((p) => p.message),
421
+ hints: pf.problems.map((p) => p.fix),
422
+ });
423
+ }
424
+ const r = await runVisuOps(resolved.path, [
425
+ {
426
+ type: "download",
427
+ connection: args.connection,
428
+ flags: args.flags ?? 0,
429
+ add_runtime: args.add_runtime ?? false,
430
+ },
431
+ ], false);
432
+ return visuResultToResponse(r, { lvpPath: resolved.path, connectionUsed: args.connection, ipUsed });
433
+ }
434
+ // apply_changes
435
+ const raw = args.operations ?? [];
436
+ const parsed = parseOperations(raw);
437
+ if ("errors" in parsed) {
438
+ return {
439
+ content: [{ type: "text", text: `Invalid operations:\n${parsed.errors.join("\n")}` }],
440
+ isError: true,
441
+ };
442
+ }
443
+ const visuOps = parsed.ops.map(toVisuOp);
444
+ const r = await runVisuOps(resolved.path, visuOps);
445
+ return visuResultToResponse(r, { lvpPath: resolved.path });
446
+ });
447
+ }