mcp-for-i 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/README.md +53 -0
- package/dist/config/store.js +365 -0
- package/dist/controlplane/autostart.js +93 -0
- package/dist/controlplane/connectionService.js +79 -0
- package/dist/controlplane/index.js +120 -0
- package/dist/controlplane/runtimeService.js +263 -0
- package/dist/controlplane/server.js +180 -0
- package/dist/controlplane/ui.js +741 -0
- package/dist/ibmi/CompileTools.js +91 -0
- package/dist/ibmi/IBMiContent.js +386 -0
- package/dist/ibmi/LocalLanguageActions.js +183 -0
- package/dist/ibmi/Search.js +135 -0
- package/dist/ibmi/Tools.js +188 -0
- package/dist/ibmi/client.js +213 -0
- package/dist/ibmi/sourceDates.js +60 -0
- package/dist/ibmi/types.js +1 -0
- package/dist/ibmi/variables.js +53 -0
- package/dist/index.js +59 -0
- package/dist/mcp/context.js +235 -0
- package/dist/mcp/logger.js +73 -0
- package/dist/mcp/tools.js +1335 -0
- package/dist/mcp/validation.js +74 -0
- package/dist/security/credentialStore.js +42 -0
- package/docs/configuration.md +57 -0
- package/docs/examples.md +73 -0
- package/docs/installation.md +61 -0
- package/docs/overview.md +14 -0
- package/docs/security.md +8 -0
- package/docs/tools.md +105 -0
- package/package.json +49 -0
|
@@ -0,0 +1,1335 @@
|
|
|
1
|
+
import { LocalLanguageActions } from "../ibmi/LocalLanguageActions.js";
|
|
2
|
+
import { CompileTools } from "../ibmi/CompileTools.js";
|
|
3
|
+
import { Search } from "../ibmi/Search.js";
|
|
4
|
+
import { Tools } from "../ibmi/Tools.js";
|
|
5
|
+
import { IBMiClient } from "../ibmi/client.js";
|
|
6
|
+
import { ConnectionService } from "../controlplane/connectionService.js";
|
|
7
|
+
import { log } from "./logger.js";
|
|
8
|
+
import fs from "fs/promises";
|
|
9
|
+
import path from "path";
|
|
10
|
+
export function getTools() {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
name: "ibmi.connect",
|
|
14
|
+
description: "Connect to a saved IBM i connection by name.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
name: { type: "string", description: "Saved connection name" }
|
|
19
|
+
},
|
|
20
|
+
required: ["name"],
|
|
21
|
+
additionalProperties: false
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "ibmi.disconnect",
|
|
26
|
+
description: "Disconnect from the active IBM i system.",
|
|
27
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" } } }
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "ibmi.session.list",
|
|
31
|
+
description: "List connected IBM i sessions.",
|
|
32
|
+
inputSchema: { type: "object", properties: {} }
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "ibmi.session.status",
|
|
36
|
+
description: "Get status for a connected session.",
|
|
37
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" } } }
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "ibmi.session.keepalive",
|
|
41
|
+
description: "Extend inactivity timeout for a session.",
|
|
42
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" } } }
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "ibmi.session.terminate",
|
|
46
|
+
description: "Terminate a session.",
|
|
47
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" } } }
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "ibmi.connections.list",
|
|
51
|
+
description: "List saved IBM i connections.",
|
|
52
|
+
inputSchema: { type: "object", properties: {} }
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "ibmi.connections.add",
|
|
56
|
+
description: "Add a saved IBM i connection (credentials are managed via local control-plane UI).",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
name: { type: "string" },
|
|
61
|
+
host: { type: "string" },
|
|
62
|
+
port: { type: "number", default: 22 },
|
|
63
|
+
username: { type: "string" },
|
|
64
|
+
privateKeyPath: { type: "string" },
|
|
65
|
+
settings: { type: "object" },
|
|
66
|
+
policy: { type: "object" }
|
|
67
|
+
},
|
|
68
|
+
required: ["name", "host", "username"],
|
|
69
|
+
additionalProperties: false
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "ibmi.connections.update",
|
|
74
|
+
description: "Update a saved IBM i connection (credentials are managed via local control-plane UI).",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
name: { type: "string" },
|
|
79
|
+
host: { type: "string" },
|
|
80
|
+
port: { type: "number" },
|
|
81
|
+
username: { type: "string" },
|
|
82
|
+
privateKeyPath: { type: "string" },
|
|
83
|
+
settings: { type: "object" },
|
|
84
|
+
policy: { type: "object" }
|
|
85
|
+
},
|
|
86
|
+
required: ["name"],
|
|
87
|
+
additionalProperties: false
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "ibmi.connections.delete",
|
|
92
|
+
description: "Delete a saved IBM i connection.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: { name: { type: "string" } },
|
|
96
|
+
required: ["name"]
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "ibmi.qsys.libraries.list",
|
|
101
|
+
description: "List libraries.",
|
|
102
|
+
inputSchema: { type: "object", properties: { filter: { type: "string" } } }
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "ibmi.qsys.objects.list",
|
|
106
|
+
description: "List objects in a library.",
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
library: { type: "string" },
|
|
111
|
+
types: { type: "array", items: { type: "string" } }
|
|
112
|
+
},
|
|
113
|
+
required: ["library"]
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "ibmi.qsys.sourcefiles.list",
|
|
118
|
+
description: "List source physical files in a library.",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: { library: { type: "string" } },
|
|
122
|
+
required: ["library"]
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "ibmi.qsys.members.list",
|
|
127
|
+
description: "List members in a source file.",
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: { library: { type: "string" }, sourceFile: { type: "string" } },
|
|
131
|
+
required: ["library", "sourceFile"]
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "ibmi.qsys.members.read",
|
|
136
|
+
description: "Read a member.",
|
|
137
|
+
inputSchema: {
|
|
138
|
+
type: "object",
|
|
139
|
+
properties: { library: { type: "string" }, sourceFile: { type: "string" }, member: { type: "string" } },
|
|
140
|
+
required: ["library", "sourceFile", "member"]
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "ibmi.qsys.members.write",
|
|
145
|
+
description: "Write a member.",
|
|
146
|
+
inputSchema: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: { library: { type: "string" }, sourceFile: { type: "string" }, member: { type: "string" }, content: { type: "string" } },
|
|
149
|
+
required: ["library", "sourceFile", "member", "content"]
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "ibmi.qsys.members.create",
|
|
154
|
+
description: "Create a member.",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: { library: { type: "string" }, sourceFile: { type: "string" }, member: { type: "string" }, srctype: { type: "string" } },
|
|
158
|
+
required: ["library", "sourceFile", "member"]
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "ibmi.qsys.members.rename",
|
|
163
|
+
description: "Rename a member.",
|
|
164
|
+
inputSchema: {
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: { library: { type: "string" }, sourceFile: { type: "string" }, member: { type: "string" }, newMember: { type: "string" } },
|
|
167
|
+
required: ["library", "sourceFile", "member", "newMember"]
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "ibmi.qsys.members.delete",
|
|
172
|
+
description: "Delete a member.",
|
|
173
|
+
inputSchema: {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties: { library: { type: "string" }, sourceFile: { type: "string" }, member: { type: "string" } },
|
|
176
|
+
required: ["library", "sourceFile", "member"]
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "ibmi.qsys.sourcefiles.create",
|
|
181
|
+
description: "Create a source physical file.",
|
|
182
|
+
inputSchema: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: { library: { type: "string" }, sourceFile: { type: "string" }, rcdlen: { type: "number" } },
|
|
185
|
+
required: ["library", "sourceFile"]
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "ibmi.qsys.libraries.create",
|
|
190
|
+
description: "Create a library.",
|
|
191
|
+
inputSchema: { type: "object", properties: { library: { type: "string" } }, required: ["library"] }
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "ibmi.ifs.list",
|
|
195
|
+
description: "List an IFS directory.",
|
|
196
|
+
inputSchema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] }
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "ibmi.ifs.read",
|
|
200
|
+
description: "Read an IFS file.",
|
|
201
|
+
inputSchema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] }
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "ibmi.ifs.write",
|
|
205
|
+
description: "Write an IFS file.",
|
|
206
|
+
inputSchema: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] }
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: "ibmi.ifs.mkdir",
|
|
210
|
+
description: "Create an IFS directory.",
|
|
211
|
+
inputSchema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] }
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: "ibmi.ifs.delete",
|
|
215
|
+
description: "Delete an IFS file or directory.",
|
|
216
|
+
inputSchema: { type: "object", properties: { path: { type: "string" }, recursive: { type: "boolean" } }, required: ["path"] }
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "ibmi.ifs.upload",
|
|
220
|
+
description: "Upload a local file to IFS.",
|
|
221
|
+
inputSchema: { type: "object", properties: { localPath: { type: "string" }, remotePath: { type: "string" } }, required: ["localPath", "remotePath"] }
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: "ibmi.ifs.download",
|
|
225
|
+
description: "Download an IFS file to local path.",
|
|
226
|
+
inputSchema: { type: "object", properties: { remotePath: { type: "string" }, localPath: { type: "string" } }, required: ["remotePath", "localPath"] }
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "ibmi.search.members",
|
|
230
|
+
description: "Search in source members.",
|
|
231
|
+
inputSchema: {
|
|
232
|
+
type: "object",
|
|
233
|
+
properties: {
|
|
234
|
+
library: { type: "string" },
|
|
235
|
+
sourceFile: { type: "string" },
|
|
236
|
+
term: { type: "string" },
|
|
237
|
+
members: { type: ["string", "array"], items: { type: "string" } }
|
|
238
|
+
},
|
|
239
|
+
required: ["library", "sourceFile", "term"]
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "ibmi.search.ifs",
|
|
244
|
+
description: "Search in IFS.",
|
|
245
|
+
inputSchema: { type: "object", properties: { path: { type: "string" }, term: { type: "string" } }, required: ["path", "term"] }
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: "ibmi.find.ifs",
|
|
249
|
+
description: "Find files by name in IFS.",
|
|
250
|
+
inputSchema: { type: "object", properties: { path: { type: "string" }, term: { type: "string" } }, required: ["path", "term"] }
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "ibmi.actions.list",
|
|
254
|
+
description: "List available actions.",
|
|
255
|
+
inputSchema: { type: "object", properties: { type: { type: "string" }, extension: { type: "string" } } }
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "ibmi.actions.run",
|
|
259
|
+
description: "Run an action.",
|
|
260
|
+
inputSchema: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {
|
|
263
|
+
actionName: { type: "string" },
|
|
264
|
+
action: { type: "object" },
|
|
265
|
+
targetType: { type: "string", enum: ["member", "streamfile", "file"] },
|
|
266
|
+
targetPath: { type: "string" },
|
|
267
|
+
library: { type: "string" },
|
|
268
|
+
sourceFile: { type: "string" },
|
|
269
|
+
member: { type: "string" },
|
|
270
|
+
extension: { type: "string" }
|
|
271
|
+
},
|
|
272
|
+
required: ["targetType", "targetPath"]
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "ibmi.actions.save",
|
|
277
|
+
description: "Create or update a custom action.",
|
|
278
|
+
inputSchema: { type: "object", properties: { action: { type: "object" } }, required: ["action"] }
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "ibmi.actions.delete",
|
|
282
|
+
description: "Delete a custom action by name.",
|
|
283
|
+
inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] }
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "ibmi.libl.get",
|
|
287
|
+
description: "Get current library list settings.",
|
|
288
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" } } }
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "ibmi.libl.set",
|
|
292
|
+
description: "Set library list and/or current library.",
|
|
293
|
+
inputSchema: {
|
|
294
|
+
type: "object",
|
|
295
|
+
properties: {
|
|
296
|
+
connectionName: { type: "string" },
|
|
297
|
+
libraryList: { type: "array", items: { type: "string" } },
|
|
298
|
+
currentLibrary: { type: "string" },
|
|
299
|
+
applyToJob: { type: "boolean", default: false }
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "ibmi.libl.add",
|
|
305
|
+
description: "Add a library to the library list.",
|
|
306
|
+
inputSchema: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: { connectionName: { type: "string" }, library: { type: "string" }, applyToJob: { type: "boolean", default: false } },
|
|
309
|
+
required: ["library"]
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "ibmi.libl.remove",
|
|
314
|
+
description: "Remove a library from the library list.",
|
|
315
|
+
inputSchema: {
|
|
316
|
+
type: "object",
|
|
317
|
+
properties: { connectionName: { type: "string" }, library: { type: "string" }, applyToJob: { type: "boolean", default: false } },
|
|
318
|
+
required: ["library"]
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "ibmi.libl.setCurrent",
|
|
323
|
+
description: "Set current library.",
|
|
324
|
+
inputSchema: {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: { connectionName: { type: "string" }, library: { type: "string" }, applyToJob: { type: "boolean", default: false } },
|
|
327
|
+
required: ["library"]
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: "ibmi.libl.validate",
|
|
332
|
+
description: "Validate a library list.",
|
|
333
|
+
inputSchema: {
|
|
334
|
+
type: "object",
|
|
335
|
+
properties: { libraryList: { type: "array", items: { type: "string" } } },
|
|
336
|
+
required: ["libraryList"]
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "ibmi.profiles.list",
|
|
341
|
+
description: "List connection profiles.",
|
|
342
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" } } }
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: "ibmi.profiles.save",
|
|
346
|
+
description: "Create or update a connection profile.",
|
|
347
|
+
inputSchema: {
|
|
348
|
+
type: "object",
|
|
349
|
+
properties: { connectionName: { type: "string" }, profile: { type: "object" } },
|
|
350
|
+
required: ["profile"]
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: "ibmi.profiles.delete",
|
|
355
|
+
description: "Delete a connection profile.",
|
|
356
|
+
inputSchema: {
|
|
357
|
+
type: "object",
|
|
358
|
+
properties: { connectionName: { type: "string" }, name: { type: "string" } },
|
|
359
|
+
required: ["name"]
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: "ibmi.profiles.activate",
|
|
364
|
+
description: "Activate a profile for a connection.",
|
|
365
|
+
inputSchema: {
|
|
366
|
+
type: "object",
|
|
367
|
+
properties: { connectionName: { type: "string" }, name: { type: "string" }, applyToJob: { type: "boolean", default: false } },
|
|
368
|
+
required: ["name"]
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: "ibmi.resolve.path",
|
|
373
|
+
description: "Resolve a member or IFS path and return details if it exists.",
|
|
374
|
+
inputSchema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] }
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: "ibmi.deploy.uploadDirectory",
|
|
378
|
+
description: "Upload a local directory to IFS.",
|
|
379
|
+
inputSchema: {
|
|
380
|
+
type: "object",
|
|
381
|
+
properties: { localPath: { type: "string" }, remotePath: { type: "string" } },
|
|
382
|
+
required: ["localPath", "remotePath"]
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: "ibmi.deploy.uploadFiles",
|
|
387
|
+
description: "Upload local files to a remote directory.",
|
|
388
|
+
inputSchema: {
|
|
389
|
+
type: "object",
|
|
390
|
+
properties: { localFiles: { type: "array", items: { type: "string" } }, remoteDirectory: { type: "string" } },
|
|
391
|
+
required: ["localFiles", "remoteDirectory"]
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: "ibmi.deploy.setCcsid",
|
|
396
|
+
description: "Set CCSID for a remote path (recursive).",
|
|
397
|
+
inputSchema: {
|
|
398
|
+
type: "object",
|
|
399
|
+
properties: { remotePath: { type: "string" }, ccsid: { type: "string", default: "1208" } },
|
|
400
|
+
required: ["remotePath"]
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
name: "ibmi.filters.list",
|
|
405
|
+
description: "List object filters.",
|
|
406
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" } } }
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: "ibmi.filters.save",
|
|
410
|
+
description: "Create or update an object filter.",
|
|
411
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" }, filter: { type: "object" } }, required: ["filter"] }
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: "ibmi.filters.delete",
|
|
415
|
+
description: "Delete an object filter by name.",
|
|
416
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" }, name: { type: "string" } }, required: ["name"] }
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
name: "ibmi.ifs.shortcuts.list",
|
|
420
|
+
description: "List IFS shortcuts.",
|
|
421
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" } } }
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "ibmi.ifs.shortcuts.add",
|
|
425
|
+
description: "Add an IFS shortcut.",
|
|
426
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" }, path: { type: "string" } }, required: ["path"] }
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "ibmi.ifs.shortcuts.delete",
|
|
430
|
+
description: "Delete an IFS shortcut.",
|
|
431
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" }, path: { type: "string" } }, required: ["path"] }
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: "ibmi.debug.status",
|
|
435
|
+
description: "Check IBM i debug service status.",
|
|
436
|
+
inputSchema: { type: "object", properties: {} }
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: "ibmi.debug.startService",
|
|
440
|
+
description: "Start IBM i debug service (best-effort).",
|
|
441
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" }, approve: { type: "boolean" } } }
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "ibmi.debug.stopService",
|
|
445
|
+
description: "Stop IBM i debug service (best-effort).",
|
|
446
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" }, approve: { type: "boolean" } } }
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: "ibmi.deploy.compare",
|
|
450
|
+
description: "Compare local and remote directories (by relative paths).",
|
|
451
|
+
inputSchema: { type: "object", properties: { localPath: { type: "string" }, remotePath: { type: "string" } }, required: ["localPath", "remotePath"] }
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: "ibmi.deploy.sync",
|
|
455
|
+
description: "Sync local directory to remote (upload missing or overwrite).",
|
|
456
|
+
inputSchema: { type: "object", properties: { localPath: { type: "string" }, remotePath: { type: "string" }, overwrite: { type: "boolean", default: false } }, required: ["localPath", "remotePath"] }
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: "ibmi.sql.query",
|
|
460
|
+
description: "Run a read-only SQL query (supports cursor pagination).",
|
|
461
|
+
inputSchema: {
|
|
462
|
+
type: "object",
|
|
463
|
+
properties: {
|
|
464
|
+
sql: { type: "string" },
|
|
465
|
+
cursor: { type: "string" },
|
|
466
|
+
pageSize: { type: "number" },
|
|
467
|
+
maxRows: { type: "number" },
|
|
468
|
+
connectionName: { type: "string" }
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "ibmi.sql.execute",
|
|
474
|
+
description: "Run SQL statements with guarded policy checks.",
|
|
475
|
+
inputSchema: {
|
|
476
|
+
type: "object",
|
|
477
|
+
properties: {
|
|
478
|
+
sql: { type: "string" },
|
|
479
|
+
connectionName: { type: "string" },
|
|
480
|
+
approve: { type: "boolean" }
|
|
481
|
+
},
|
|
482
|
+
required: ["sql"]
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: "ibmi.cl.run",
|
|
487
|
+
description: "Run arbitrary CL command with policy checks.",
|
|
488
|
+
inputSchema: {
|
|
489
|
+
type: "object",
|
|
490
|
+
properties: {
|
|
491
|
+
command: { type: "string" },
|
|
492
|
+
environment: { type: "string", enum: ["ile", "qsh", "pase"] },
|
|
493
|
+
cwd: { type: "string" },
|
|
494
|
+
connectionName: { type: "string" },
|
|
495
|
+
approve: { type: "boolean" }
|
|
496
|
+
},
|
|
497
|
+
required: ["command"]
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: "ibmi.diagnostics.parseEvfevent",
|
|
502
|
+
description: "Parse EVFEVENT text into structured diagnostics.",
|
|
503
|
+
inputSchema: { type: "object", properties: { content: { type: "string" } }, required: ["content"] }
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: "ibmi.joblog.get",
|
|
507
|
+
description: "Read recent job log messages for the current job.",
|
|
508
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" }, limit: { type: "number" } } }
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: "ibmi.spool.list",
|
|
512
|
+
description: "List spool file entries.",
|
|
513
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" }, limit: { type: "number" } } }
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: "ibmi.spool.read",
|
|
517
|
+
description: "Read line data for a spool file entry.",
|
|
518
|
+
inputSchema: {
|
|
519
|
+
type: "object",
|
|
520
|
+
properties: {
|
|
521
|
+
jobName: { type: "string" },
|
|
522
|
+
spooledFileName: { type: "string" },
|
|
523
|
+
spooledFileNumber: { type: "number" },
|
|
524
|
+
limit: { type: "number" },
|
|
525
|
+
connectionName: { type: "string" }
|
|
526
|
+
},
|
|
527
|
+
required: ["jobName", "spooledFileName", "spooledFileNumber"]
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: "ibmi.tn5250.connect",
|
|
532
|
+
description: "Connect to TN5250 (phase scaffold).",
|
|
533
|
+
inputSchema: { type: "object", properties: { connectionName: { type: "string" } } }
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: "ibmi.tn5250.readScreen",
|
|
537
|
+
description: "Read TN5250 screen model (phase scaffold).",
|
|
538
|
+
inputSchema: { type: "object", properties: {} }
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: "ibmi.tn5250.setField",
|
|
542
|
+
description: "Set TN5250 field (phase scaffold).",
|
|
543
|
+
inputSchema: { type: "object", properties: { fieldId: { type: "string" }, value: { type: "string" } }, required: ["fieldId", "value"] }
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "ibmi.tn5250.sendKeys",
|
|
547
|
+
description: "Send TN5250 keys (phase scaffold).",
|
|
548
|
+
inputSchema: { type: "object", properties: { keys: { type: "string" } }, required: ["keys"] }
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name: "ibmi.tn5250.waitFor",
|
|
552
|
+
description: "Wait for TN5250 condition (phase scaffold).",
|
|
553
|
+
inputSchema: { type: "object", properties: { text: { type: "string" }, timeoutMs: { type: "number" } } }
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "ibmi.tn5250.snapshot",
|
|
557
|
+
description: "Capture TN5250 snapshot (phase scaffold).",
|
|
558
|
+
inputSchema: { type: "object", properties: {} }
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: "ibmi.tn5250.disconnect",
|
|
562
|
+
description: "Disconnect TN5250 session (phase scaffold).",
|
|
563
|
+
inputSchema: { type: "object", properties: {} }
|
|
564
|
+
}
|
|
565
|
+
];
|
|
566
|
+
}
|
|
567
|
+
export async function handleTool(ctx, name, args) {
|
|
568
|
+
const connections = new ConnectionService(ctx.store);
|
|
569
|
+
switch (name) {
|
|
570
|
+
case "ibmi.connect": {
|
|
571
|
+
if (!args?.name)
|
|
572
|
+
throw new Error("name is required");
|
|
573
|
+
if (args?.host || args?.username || args?.password || args?.privateKeyPath) {
|
|
574
|
+
throw new Error("Direct credential/host arguments are disabled. Save a connection and connect by name.");
|
|
575
|
+
}
|
|
576
|
+
await ctx.connectByName(args.name);
|
|
577
|
+
return result(`Connected to ${args.name}`);
|
|
578
|
+
}
|
|
579
|
+
case "ibmi.disconnect": {
|
|
580
|
+
await ctx.disconnect(args?.connectionName);
|
|
581
|
+
return result("Disconnected");
|
|
582
|
+
}
|
|
583
|
+
case "ibmi.session.list": {
|
|
584
|
+
return json(ctx.listSessions());
|
|
585
|
+
}
|
|
586
|
+
case "ibmi.session.status": {
|
|
587
|
+
return json(ctx.statusSession(args?.connectionName));
|
|
588
|
+
}
|
|
589
|
+
case "ibmi.session.keepalive": {
|
|
590
|
+
return json(ctx.keepaliveSession(args?.connectionName));
|
|
591
|
+
}
|
|
592
|
+
case "ibmi.session.terminate": {
|
|
593
|
+
await ctx.terminateSession(args?.connectionName);
|
|
594
|
+
return result("Terminated");
|
|
595
|
+
}
|
|
596
|
+
case "ibmi.connections.list": {
|
|
597
|
+
const list = await connections.list();
|
|
598
|
+
return json(list);
|
|
599
|
+
}
|
|
600
|
+
case "ibmi.connections.add": {
|
|
601
|
+
rejectCredentialArgs(args, "ibmi.connections.add");
|
|
602
|
+
await connections.add(args);
|
|
603
|
+
return result(`Connection ${args.name} saved`);
|
|
604
|
+
}
|
|
605
|
+
case "ibmi.connections.update": {
|
|
606
|
+
rejectCredentialArgs(args, "ibmi.connections.update");
|
|
607
|
+
await connections.update(args.name, args);
|
|
608
|
+
return result(`Connection ${args.name} updated`);
|
|
609
|
+
}
|
|
610
|
+
case "ibmi.connections.delete": {
|
|
611
|
+
await ctx.disconnect(args.name);
|
|
612
|
+
await connections.delete(args.name);
|
|
613
|
+
return result(`Connection ${args.name} deleted`);
|
|
614
|
+
}
|
|
615
|
+
case "ibmi.qsys.libraries.list": {
|
|
616
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
617
|
+
const data = await conn.content.getLibraries(args.filter || "*");
|
|
618
|
+
return json(data);
|
|
619
|
+
}
|
|
620
|
+
case "ibmi.qsys.objects.list": {
|
|
621
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
622
|
+
const data = await conn.content.getObjectList(args.library, args.types || ["*ALL"]);
|
|
623
|
+
return json(data);
|
|
624
|
+
}
|
|
625
|
+
case "ibmi.qsys.sourcefiles.list": {
|
|
626
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
627
|
+
const data = await conn.content.getObjectList(args.library, ["*SRCPF"]);
|
|
628
|
+
return json(data);
|
|
629
|
+
}
|
|
630
|
+
case "ibmi.qsys.members.list": {
|
|
631
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
632
|
+
const data = await conn.content.getMemberList({ library: args.library, sourceFile: args.sourceFile });
|
|
633
|
+
return json(data);
|
|
634
|
+
}
|
|
635
|
+
case "ibmi.qsys.members.read": {
|
|
636
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
637
|
+
const content = await conn.content.downloadMemberContent(args.library, args.sourceFile, args.member);
|
|
638
|
+
return result(content);
|
|
639
|
+
}
|
|
640
|
+
case "ibmi.qsys.members.write": {
|
|
641
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
642
|
+
enforceWritable(ctx, conn, "qsys.write", args);
|
|
643
|
+
await conn.content.uploadMemberContent(args.library, args.sourceFile, args.member, args.content);
|
|
644
|
+
return result("OK");
|
|
645
|
+
}
|
|
646
|
+
case "ibmi.qsys.members.create": {
|
|
647
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
648
|
+
enforceWritable(ctx, conn, "qsys.write", args);
|
|
649
|
+
await conn.content.createMember(args.library, args.sourceFile, args.member, args.srctype);
|
|
650
|
+
return result("OK");
|
|
651
|
+
}
|
|
652
|
+
case "ibmi.qsys.members.rename": {
|
|
653
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
654
|
+
enforceWritable(ctx, conn, "qsys.write", args);
|
|
655
|
+
await conn.content.renameMember(args.library, args.sourceFile, args.member, args.newMember);
|
|
656
|
+
return result("OK");
|
|
657
|
+
}
|
|
658
|
+
case "ibmi.qsys.members.delete": {
|
|
659
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
660
|
+
enforceWritable(ctx, conn, "qsys.write", args);
|
|
661
|
+
await conn.content.deleteMember(args.library, args.sourceFile, args.member);
|
|
662
|
+
return result("OK");
|
|
663
|
+
}
|
|
664
|
+
case "ibmi.qsys.sourcefiles.create": {
|
|
665
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
666
|
+
enforceWritable(ctx, conn, "qsys.write", args);
|
|
667
|
+
await conn.content.createSourceFile(args.library, args.sourceFile, args.rcdlen || 112);
|
|
668
|
+
return result("OK");
|
|
669
|
+
}
|
|
670
|
+
case "ibmi.qsys.libraries.create": {
|
|
671
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
672
|
+
enforceWritable(ctx, conn, "qsys.write", args);
|
|
673
|
+
await conn.content.createLibrary(args.library);
|
|
674
|
+
return result("OK");
|
|
675
|
+
}
|
|
676
|
+
case "ibmi.ifs.list": {
|
|
677
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
678
|
+
const data = await conn.content.getFileList(args.path);
|
|
679
|
+
return json(data);
|
|
680
|
+
}
|
|
681
|
+
case "ibmi.ifs.read": {
|
|
682
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
683
|
+
const data = await conn.content.downloadStreamfileRaw(args.path);
|
|
684
|
+
return result(Buffer.from(data).toString("utf8"));
|
|
685
|
+
}
|
|
686
|
+
case "ibmi.ifs.write": {
|
|
687
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
688
|
+
enforceWritable(ctx, conn, "ifs.write", args);
|
|
689
|
+
await conn.content.writeStreamfileRaw(args.path, args.content);
|
|
690
|
+
return result("OK");
|
|
691
|
+
}
|
|
692
|
+
case "ibmi.ifs.mkdir": {
|
|
693
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
694
|
+
enforceWritable(ctx, conn, "ifs.write", args);
|
|
695
|
+
await conn.sendCommand({ command: `mkdir -p ${Tools.escapePath(args.path)}` });
|
|
696
|
+
return result("OK");
|
|
697
|
+
}
|
|
698
|
+
case "ibmi.ifs.delete": {
|
|
699
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
700
|
+
enforceWritable(ctx, conn, "ifs.delete", args);
|
|
701
|
+
const flag = args.recursive ? "-rf" : "-f";
|
|
702
|
+
await conn.sendCommand({ command: `rm ${flag} ${Tools.escapePath(args.path)}` });
|
|
703
|
+
return result("OK");
|
|
704
|
+
}
|
|
705
|
+
case "ibmi.ifs.upload": {
|
|
706
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
707
|
+
enforceWritable(ctx, conn, "ifs.write", args);
|
|
708
|
+
await conn.client.putFile(args.localPath, args.remotePath);
|
|
709
|
+
return result("OK");
|
|
710
|
+
}
|
|
711
|
+
case "ibmi.ifs.download": {
|
|
712
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
713
|
+
await conn.client.getFile(args.localPath, args.remotePath);
|
|
714
|
+
return result("OK");
|
|
715
|
+
}
|
|
716
|
+
case "ibmi.search.members": {
|
|
717
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
718
|
+
const members = args.members || "*";
|
|
719
|
+
const data = await Search.searchMembers(conn, args.library, args.sourceFile, args.term, members, false);
|
|
720
|
+
return json(data);
|
|
721
|
+
}
|
|
722
|
+
case "ibmi.search.ifs": {
|
|
723
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
724
|
+
const data = await Search.searchIFS(conn, args.path, args.term);
|
|
725
|
+
return json(data ?? { term: args.term, hits: [] });
|
|
726
|
+
}
|
|
727
|
+
case "ibmi.find.ifs": {
|
|
728
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
729
|
+
const data = await Search.findIFS(conn, args.path, args.term);
|
|
730
|
+
return json(data ?? { term: args.term, hits: [] });
|
|
731
|
+
}
|
|
732
|
+
case "ibmi.actions.list": {
|
|
733
|
+
let actions = await listActions(ctx);
|
|
734
|
+
if (args?.type) {
|
|
735
|
+
actions = actions.filter(a => (a.type || "member") === args.type);
|
|
736
|
+
}
|
|
737
|
+
if (args?.extension) {
|
|
738
|
+
const ext = String(args.extension).toUpperCase();
|
|
739
|
+
actions = actions.filter(a => !a.extensions || a.extensions.includes("GLOBAL") || a.extensions.map(e => e.toUpperCase()).includes(ext));
|
|
740
|
+
}
|
|
741
|
+
return json(actions);
|
|
742
|
+
}
|
|
743
|
+
case "ibmi.actions.run": {
|
|
744
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
745
|
+
enforceWritable(ctx, conn, "qsys.write", args);
|
|
746
|
+
const action = await resolveAction(ctx, args);
|
|
747
|
+
if (!action)
|
|
748
|
+
throw new Error("Action not found");
|
|
749
|
+
const envVars = {};
|
|
750
|
+
const targetPath = args.targetPath;
|
|
751
|
+
const targetType = args.targetType;
|
|
752
|
+
if (targetType === "file") {
|
|
753
|
+
throw new Error("Local file actions are not supported in this MCP server (use streamfile or member).");
|
|
754
|
+
}
|
|
755
|
+
if (targetType === "streamfile") {
|
|
756
|
+
envVars["&FULLPATH"] = targetPath;
|
|
757
|
+
const base = targetPath.split("/").pop() || targetPath;
|
|
758
|
+
envVars["&BASENAME"] = base;
|
|
759
|
+
envVars["&NAME"] = base.includes(".") ? base.substring(0, base.lastIndexOf(".")) : base;
|
|
760
|
+
envVars["&EXT"] = base.includes(".") ? base.substring(base.lastIndexOf(".") + 1) : "";
|
|
761
|
+
envVars["&RELATIVEPATH"] = targetPath;
|
|
762
|
+
}
|
|
763
|
+
if (targetType === "member") {
|
|
764
|
+
const lib = String(args.library || "").toUpperCase();
|
|
765
|
+
const src = String(args.sourceFile || "").toUpperCase();
|
|
766
|
+
const mbr = String(args.member || targetPath.split("/").pop() || "").toUpperCase();
|
|
767
|
+
const ext = String(args.extension || "");
|
|
768
|
+
envVars["&LIBRARY"] = lib;
|
|
769
|
+
envVars["&SRCFILE"] = src;
|
|
770
|
+
envVars["&NAME"] = mbr;
|
|
771
|
+
envVars["&EXT"] = ext;
|
|
772
|
+
envVars["&EXTL"] = ext.toLowerCase();
|
|
773
|
+
envVars["&FULLPATH"] = `${lib}/${src}/${mbr}`;
|
|
774
|
+
envVars["&OPENLIB"] = lib;
|
|
775
|
+
envVars["&OPENLIBL"] = lib.toLowerCase();
|
|
776
|
+
envVars["&OPENSPF"] = src;
|
|
777
|
+
envVars["&OPENSPFL"] = src.toLowerCase();
|
|
778
|
+
envVars["&OPENMBR"] = mbr;
|
|
779
|
+
envVars["&OPENMBRL"] = mbr.toLowerCase();
|
|
780
|
+
}
|
|
781
|
+
const resultCmd = await CompileTools.runCommand(conn, {
|
|
782
|
+
command: action.command,
|
|
783
|
+
environment: action.environment,
|
|
784
|
+
env: envVars
|
|
785
|
+
});
|
|
786
|
+
return json(resultCmd);
|
|
787
|
+
}
|
|
788
|
+
case "ibmi.actions.save": {
|
|
789
|
+
const action = args.action;
|
|
790
|
+
if (!action?.name)
|
|
791
|
+
throw new Error("Action must include a name");
|
|
792
|
+
await ctx.store.upsertAction(action);
|
|
793
|
+
return result(`Action ${action.name} saved`);
|
|
794
|
+
}
|
|
795
|
+
case "ibmi.actions.delete": {
|
|
796
|
+
await ctx.store.deleteAction(args.name);
|
|
797
|
+
return result(`Action ${args.name} deleted`);
|
|
798
|
+
}
|
|
799
|
+
case "ibmi.libl.get": {
|
|
800
|
+
const name = getConnectionName(ctx, args);
|
|
801
|
+
const conn = await ctx.store.getConnection(name);
|
|
802
|
+
if (!conn)
|
|
803
|
+
throw new Error(`Connection ${name} not found`);
|
|
804
|
+
const settings = conn.settings || {};
|
|
805
|
+
let activeConfig = {};
|
|
806
|
+
try {
|
|
807
|
+
activeConfig = ctx.ensureActive(name).getConfig();
|
|
808
|
+
}
|
|
809
|
+
catch {
|
|
810
|
+
activeConfig = {};
|
|
811
|
+
}
|
|
812
|
+
return json({
|
|
813
|
+
currentLibrary: settings.currentLibrary ?? activeConfig.currentLibrary,
|
|
814
|
+
libraryList: settings.libraryList ?? activeConfig.libraryList ?? []
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
case "ibmi.libl.set": {
|
|
818
|
+
const name = getConnectionName(ctx, args);
|
|
819
|
+
const active = ctx.ensureActive(name);
|
|
820
|
+
enforceWritable(ctx, active, "qsys.write", args);
|
|
821
|
+
await updateConnectionSettings(ctx, name, {
|
|
822
|
+
currentLibrary: args.currentLibrary,
|
|
823
|
+
libraryList: args.libraryList
|
|
824
|
+
});
|
|
825
|
+
if (args.applyToJob) {
|
|
826
|
+
await applyLibraryList(active, args.libraryList, args.currentLibrary);
|
|
827
|
+
}
|
|
828
|
+
return result("OK");
|
|
829
|
+
}
|
|
830
|
+
case "ibmi.libl.add": {
|
|
831
|
+
const name = getConnectionName(ctx, args);
|
|
832
|
+
const conn = await ctx.store.getConnection(name);
|
|
833
|
+
if (!conn)
|
|
834
|
+
throw new Error(`Connection ${name} not found`);
|
|
835
|
+
const list = conn.settings?.libraryList || [];
|
|
836
|
+
const lib = String(args.library).toUpperCase();
|
|
837
|
+
if (!list.includes(lib))
|
|
838
|
+
list.push(lib);
|
|
839
|
+
const active = ctx.ensureActive(name);
|
|
840
|
+
enforceWritable(ctx, active, "qsys.write", args);
|
|
841
|
+
await updateConnectionSettings(ctx, name, { libraryList: list });
|
|
842
|
+
if (args.applyToJob) {
|
|
843
|
+
await applyLibraryList(active, list, conn.settings?.currentLibrary);
|
|
844
|
+
}
|
|
845
|
+
return result("OK");
|
|
846
|
+
}
|
|
847
|
+
case "ibmi.libl.remove": {
|
|
848
|
+
const name = getConnectionName(ctx, args);
|
|
849
|
+
const conn = await ctx.store.getConnection(name);
|
|
850
|
+
if (!conn)
|
|
851
|
+
throw new Error(`Connection ${name} not found`);
|
|
852
|
+
const list = (conn.settings?.libraryList || []).filter(l => l !== String(args.library).toUpperCase());
|
|
853
|
+
const active = ctx.ensureActive(name);
|
|
854
|
+
enforceWritable(ctx, active, "qsys.write", args);
|
|
855
|
+
await updateConnectionSettings(ctx, name, { libraryList: list });
|
|
856
|
+
if (args.applyToJob) {
|
|
857
|
+
await applyLibraryList(active, list, conn.settings?.currentLibrary);
|
|
858
|
+
}
|
|
859
|
+
return result("OK");
|
|
860
|
+
}
|
|
861
|
+
case "ibmi.libl.setCurrent": {
|
|
862
|
+
const name = getConnectionName(ctx, args);
|
|
863
|
+
const lib = String(args.library).toUpperCase();
|
|
864
|
+
const active = ctx.ensureActive(name);
|
|
865
|
+
enforceWritable(ctx, active, "qsys.write", args);
|
|
866
|
+
await updateConnectionSettings(ctx, name, { currentLibrary: lib });
|
|
867
|
+
if (args.applyToJob) {
|
|
868
|
+
await applyLibraryList(active, undefined, lib);
|
|
869
|
+
}
|
|
870
|
+
return result("OK");
|
|
871
|
+
}
|
|
872
|
+
case "ibmi.libl.validate": {
|
|
873
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
874
|
+
const bad = await conn.content.validateLibraryList(args.libraryList);
|
|
875
|
+
return json({ badLibraries: bad });
|
|
876
|
+
}
|
|
877
|
+
case "ibmi.profiles.list": {
|
|
878
|
+
const name = getConnectionName(ctx, args);
|
|
879
|
+
const profiles = await ctx.store.listProfiles(name);
|
|
880
|
+
return json(profiles);
|
|
881
|
+
}
|
|
882
|
+
case "ibmi.profiles.save": {
|
|
883
|
+
const name = getConnectionName(ctx, args);
|
|
884
|
+
const profile = args.profile;
|
|
885
|
+
if (!profile?.name)
|
|
886
|
+
throw new Error("Profile must include a name");
|
|
887
|
+
await ctx.store.saveProfile(name, profile);
|
|
888
|
+
return result(`Profile ${profile.name} saved`);
|
|
889
|
+
}
|
|
890
|
+
case "ibmi.profiles.delete": {
|
|
891
|
+
const name = getConnectionName(ctx, args);
|
|
892
|
+
await ctx.store.deleteProfile(name, args.name);
|
|
893
|
+
return result(`Profile ${args.name} deleted`);
|
|
894
|
+
}
|
|
895
|
+
case "ibmi.profiles.activate": {
|
|
896
|
+
const name = getConnectionName(ctx, args);
|
|
897
|
+
const connInfo = await ctx.store.getConnection(name);
|
|
898
|
+
if (!connInfo)
|
|
899
|
+
throw new Error(`Connection ${name} not found`);
|
|
900
|
+
const profile = (connInfo.profiles || []).find(p => p.name === args.name);
|
|
901
|
+
if (!profile)
|
|
902
|
+
throw new Error(`Profile ${args.name} not found`);
|
|
903
|
+
const active = ctx.ensureActive(name);
|
|
904
|
+
enforceWritable(ctx, active, "qsys.write", args);
|
|
905
|
+
await ctx.store.setCurrentProfile(name, args.name);
|
|
906
|
+
await updateConnectionSettings(ctx, name, {
|
|
907
|
+
currentLibrary: profile.currentLibrary,
|
|
908
|
+
libraryList: profile.libraryList,
|
|
909
|
+
customVariables: profile.customVariables
|
|
910
|
+
});
|
|
911
|
+
if (args.applyToJob) {
|
|
912
|
+
await applyLibraryList(active, profile.libraryList, profile.currentLibrary);
|
|
913
|
+
}
|
|
914
|
+
return result(`Profile ${args.name} activated`);
|
|
915
|
+
}
|
|
916
|
+
case "ibmi.resolve.path": {
|
|
917
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
918
|
+
const pathStr = String(args.path);
|
|
919
|
+
if (pathStr.startsWith("/")) {
|
|
920
|
+
const isDir = await conn.content.testStreamFile(pathStr, "d");
|
|
921
|
+
const isFile = await conn.content.testStreamFile(pathStr, "e");
|
|
922
|
+
return json({ type: "ifs", exists: isDir || isFile, kind: isDir ? "directory" : isFile ? "streamfile" : "unknown", path: pathStr });
|
|
923
|
+
}
|
|
924
|
+
const parts = pathStr.split("/").filter(Boolean);
|
|
925
|
+
if (parts.length < 3)
|
|
926
|
+
throw new Error("Member path should be LIB/FILE/MEMBER(.ext)");
|
|
927
|
+
const lib = parts.length === 4 ? parts[1] : parts[0];
|
|
928
|
+
const file = parts.length === 4 ? parts[2] : parts[1];
|
|
929
|
+
const memberPart = parts.length === 4 ? parts[3] : parts[2];
|
|
930
|
+
const member = memberPart.includes(".") ? memberPart.substring(0, memberPart.indexOf(".")) : memberPart;
|
|
931
|
+
const info = await conn.content.getMemberInfo(lib, file, member);
|
|
932
|
+
return json({ type: "member", exists: Boolean(info), details: info || { library: lib, file, name: member } });
|
|
933
|
+
}
|
|
934
|
+
case "ibmi.deploy.uploadDirectory": {
|
|
935
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
936
|
+
enforceWritable(ctx, conn, "deploy.sync", args);
|
|
937
|
+
await conn.client.putDirectory(args.localPath, args.remotePath, { recursive: true, concurrency: 5 });
|
|
938
|
+
return result("OK");
|
|
939
|
+
}
|
|
940
|
+
case "ibmi.deploy.uploadFiles": {
|
|
941
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
942
|
+
enforceWritable(ctx, conn, "deploy.sync", args);
|
|
943
|
+
const remoteDir = String(args.remoteDirectory);
|
|
944
|
+
for (const file of args.localFiles || []) {
|
|
945
|
+
const base = String(file).split(/[\\/]/).pop();
|
|
946
|
+
const remotePath = `${remoteDir}/${base}`;
|
|
947
|
+
await conn.client.putFile(String(file), remotePath);
|
|
948
|
+
}
|
|
949
|
+
return result("OK");
|
|
950
|
+
}
|
|
951
|
+
case "ibmi.deploy.setCcsid": {
|
|
952
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
953
|
+
enforceWritable(ctx, conn, "deploy.sync", args);
|
|
954
|
+
const setccsid = conn.remoteFeatures.setccsid;
|
|
955
|
+
if (!setccsid)
|
|
956
|
+
throw new Error("setccsid not available on remote system");
|
|
957
|
+
const ccsid = args.ccsid || "1208";
|
|
958
|
+
await conn.sendCommand({ command: `${setccsid} -R ${Tools.shellQuote(ccsid)} ${Tools.escapePath(args.remotePath)}` });
|
|
959
|
+
return result("OK");
|
|
960
|
+
}
|
|
961
|
+
case "ibmi.filters.list": {
|
|
962
|
+
const name = getConnectionName(ctx, args);
|
|
963
|
+
const connInfo = await ctx.store.getConnection(name);
|
|
964
|
+
if (!connInfo)
|
|
965
|
+
throw new Error(`Connection ${name} not found`);
|
|
966
|
+
return json(connInfo.settings?.objectFilters || []);
|
|
967
|
+
}
|
|
968
|
+
case "ibmi.filters.save": {
|
|
969
|
+
const name = getConnectionName(ctx, args);
|
|
970
|
+
const connInfo = await ctx.store.getConnection(name);
|
|
971
|
+
if (!connInfo)
|
|
972
|
+
throw new Error(`Connection ${name} not found`);
|
|
973
|
+
const filter = args.filter;
|
|
974
|
+
if (!filter?.name)
|
|
975
|
+
throw new Error("Filter must include a name");
|
|
976
|
+
const list = connInfo.settings?.objectFilters || [];
|
|
977
|
+
const idx = list.findIndex((f) => f.name === filter.name);
|
|
978
|
+
if (idx >= 0)
|
|
979
|
+
list[idx] = filter;
|
|
980
|
+
else
|
|
981
|
+
list.push(filter);
|
|
982
|
+
await updateConnectionSettings(ctx, name, { objectFilters: list });
|
|
983
|
+
return result(`Filter ${filter.name} saved`);
|
|
984
|
+
}
|
|
985
|
+
case "ibmi.filters.delete": {
|
|
986
|
+
const name = getConnectionName(ctx, args);
|
|
987
|
+
const connInfo = await ctx.store.getConnection(name);
|
|
988
|
+
if (!connInfo)
|
|
989
|
+
throw new Error(`Connection ${name} not found`);
|
|
990
|
+
const list = (connInfo.settings?.objectFilters || []).filter((f) => f.name !== args.name);
|
|
991
|
+
await updateConnectionSettings(ctx, name, { objectFilters: list });
|
|
992
|
+
return result(`Filter ${args.name} deleted`);
|
|
993
|
+
}
|
|
994
|
+
case "ibmi.ifs.shortcuts.list": {
|
|
995
|
+
const name = getConnectionName(ctx, args);
|
|
996
|
+
const connInfo = await ctx.store.getConnection(name);
|
|
997
|
+
if (!connInfo)
|
|
998
|
+
throw new Error(`Connection ${name} not found`);
|
|
999
|
+
return json(connInfo.settings?.ifsShortcuts || []);
|
|
1000
|
+
}
|
|
1001
|
+
case "ibmi.ifs.shortcuts.add": {
|
|
1002
|
+
const name = getConnectionName(ctx, args);
|
|
1003
|
+
const connInfo = await ctx.store.getConnection(name);
|
|
1004
|
+
if (!connInfo)
|
|
1005
|
+
throw new Error(`Connection ${name} not found`);
|
|
1006
|
+
const list = connInfo.settings?.ifsShortcuts || [];
|
|
1007
|
+
if (!list.includes(args.path))
|
|
1008
|
+
list.push(args.path);
|
|
1009
|
+
await updateConnectionSettings(ctx, name, { ifsShortcuts: list });
|
|
1010
|
+
return result("OK");
|
|
1011
|
+
}
|
|
1012
|
+
case "ibmi.ifs.shortcuts.delete": {
|
|
1013
|
+
const name = getConnectionName(ctx, args);
|
|
1014
|
+
const connInfo = await ctx.store.getConnection(name);
|
|
1015
|
+
if (!connInfo)
|
|
1016
|
+
throw new Error(`Connection ${name} not found`);
|
|
1017
|
+
const list = (connInfo.settings?.ifsShortcuts || []).filter((p) => p !== args.path);
|
|
1018
|
+
await updateConnectionSettings(ctx, name, { ifsShortcuts: list });
|
|
1019
|
+
return result("OK");
|
|
1020
|
+
}
|
|
1021
|
+
case "ibmi.debug.status": {
|
|
1022
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1023
|
+
const port = conn.getConfig().debugPort || 8005;
|
|
1024
|
+
const rows = await conn.runSQL(`select job_name, local_port from qsys2.netstat_job_info where local_port = ${port} and remote_address = '0.0.0.0' fetch first 1 row only`);
|
|
1025
|
+
if (rows.length === 0)
|
|
1026
|
+
return json({ running: false, port });
|
|
1027
|
+
return json({ running: true, port, job: rows[0].JOB_NAME });
|
|
1028
|
+
}
|
|
1029
|
+
case "ibmi.debug.startService": {
|
|
1030
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1031
|
+
enforceWritable(ctx, conn, "qsys.write", args);
|
|
1032
|
+
const submitOptions = `JOBQ(QSYS/QUSRNOMAX) JOBD(QSYS/QSYSJOBD) OUTQ(QUSRSYS/QDBGSRV) USER(QDBGSRV)`;
|
|
1033
|
+
const cmd = `QSYS/SBMJOB JOB(QDBGSRV) SYSLIBL(*SYSVAL) CURLIB(*USRPRF) INLLIBL(*JOBD) ${submitOptions} CMD(QSH CMD('/QIBM/ProdData/IBMiDebugService/bin/startDebugService.sh'))`;
|
|
1034
|
+
const res = await conn.sendQsh({ command: `system \"${cmd}\"` });
|
|
1035
|
+
return json(res);
|
|
1036
|
+
}
|
|
1037
|
+
case "ibmi.debug.stopService": {
|
|
1038
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1039
|
+
enforceWritable(ctx, conn, "qsys.write", args);
|
|
1040
|
+
const res = await conn.sendCommand({ command: `/QIBM/ProdData/IBMiDebugService/bin/stopDebugService.sh` });
|
|
1041
|
+
return json(res);
|
|
1042
|
+
}
|
|
1043
|
+
case "ibmi.deploy.compare": {
|
|
1044
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1045
|
+
const localPath = String(args.localPath);
|
|
1046
|
+
const remotePath = String(args.remotePath);
|
|
1047
|
+
const localFiles = await listLocalFiles(localPath);
|
|
1048
|
+
const remoteFiles = await listRemoteFiles(conn, remotePath);
|
|
1049
|
+
const localSet = new Set(localFiles);
|
|
1050
|
+
const remoteSet = new Set(remoteFiles);
|
|
1051
|
+
const onlyLocal = localFiles.filter(f => !remoteSet.has(f));
|
|
1052
|
+
const onlyRemote = remoteFiles.filter(f => !localSet.has(f));
|
|
1053
|
+
const common = localFiles.filter(f => remoteSet.has(f));
|
|
1054
|
+
return json({ onlyLocal, onlyRemote, common });
|
|
1055
|
+
}
|
|
1056
|
+
case "ibmi.deploy.sync": {
|
|
1057
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1058
|
+
enforceWritable(ctx, conn, "deploy.sync", args);
|
|
1059
|
+
const localPath = String(args.localPath);
|
|
1060
|
+
const remotePath = String(args.remotePath);
|
|
1061
|
+
const overwrite = Boolean(args.overwrite);
|
|
1062
|
+
const localFiles = await listLocalFiles(localPath);
|
|
1063
|
+
const remoteFiles = await listRemoteFiles(conn, remotePath);
|
|
1064
|
+
const remoteSet = new Set(remoteFiles);
|
|
1065
|
+
const createdDirs = new Set();
|
|
1066
|
+
for (const rel of localFiles) {
|
|
1067
|
+
if (!overwrite && remoteSet.has(rel))
|
|
1068
|
+
continue;
|
|
1069
|
+
const localFile = path.join(localPath, rel);
|
|
1070
|
+
const remoteFile = `${remotePath}/${rel}`.replace(/\\/g, "/");
|
|
1071
|
+
const remoteDir = remoteFile.split("/").slice(0, -1).join("/");
|
|
1072
|
+
if (!createdDirs.has(remoteDir)) {
|
|
1073
|
+
await conn.sendCommand({ command: `mkdir -p ${Tools.escapePath(remoteDir)}` });
|
|
1074
|
+
createdDirs.add(remoteDir);
|
|
1075
|
+
}
|
|
1076
|
+
await conn.client.putFile(localFile, remoteFile);
|
|
1077
|
+
}
|
|
1078
|
+
return result("OK");
|
|
1079
|
+
}
|
|
1080
|
+
case "ibmi.sql.query": {
|
|
1081
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1082
|
+
const pageSize = clampNumber(args?.pageSize, 200, 1, 5000);
|
|
1083
|
+
const maxRows = clampNumber(args?.maxRows, 1000, 1, 20000);
|
|
1084
|
+
if (args?.cursor) {
|
|
1085
|
+
const next = ctx.consumeSqlCursor(String(args.cursor), pageSize);
|
|
1086
|
+
return json({ rows: next.rows, cursor: next.cursor });
|
|
1087
|
+
}
|
|
1088
|
+
if (!args?.sql)
|
|
1089
|
+
throw new Error("sql is required when cursor is not provided");
|
|
1090
|
+
const sql = String(args.sql);
|
|
1091
|
+
if (!isReadOnlySql(sql))
|
|
1092
|
+
throw new Error("ibmi.sql.query only supports read-only SQL");
|
|
1093
|
+
const rows = await conn.runSQL(sql);
|
|
1094
|
+
const limited = rows.slice(0, maxRows);
|
|
1095
|
+
const firstPage = limited.slice(0, pageSize);
|
|
1096
|
+
const cursorInfo = ctx.createSqlCursor(limited, pageSize);
|
|
1097
|
+
return json({
|
|
1098
|
+
rows: firstPage,
|
|
1099
|
+
cursor: cursorInfo.cursor,
|
|
1100
|
+
totalRows: limited.length,
|
|
1101
|
+
truncated: rows.length > limited.length
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
case "ibmi.sql.execute": {
|
|
1105
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1106
|
+
enforcePolicyOperation(ctx, "sql.write", args);
|
|
1107
|
+
const sql = String(args.sql);
|
|
1108
|
+
const rows = await conn.runSQL(sql);
|
|
1109
|
+
log("info", "audit.sql.execute", { connectionName: args?.connectionName || ctx.activeName, readOnly: isReadOnlySql(sql) });
|
|
1110
|
+
return json({ ok: true, rows });
|
|
1111
|
+
}
|
|
1112
|
+
case "ibmi.cl.run": {
|
|
1113
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1114
|
+
enforcePolicyOperation(ctx, "cl.run", args);
|
|
1115
|
+
const command = String(args.command || "");
|
|
1116
|
+
if (!command.trim())
|
|
1117
|
+
throw new Error("command is required");
|
|
1118
|
+
const environment = String(args.environment || "ile");
|
|
1119
|
+
const cwd = args.cwd ? String(args.cwd) : undefined;
|
|
1120
|
+
let cmdResult;
|
|
1121
|
+
if (environment === "pase") {
|
|
1122
|
+
cmdResult = await conn.sendCommand({ command, directory: cwd });
|
|
1123
|
+
}
|
|
1124
|
+
else if (environment === "qsh") {
|
|
1125
|
+
cmdResult = await conn.sendQsh({ command, directory: cwd });
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
cmdResult = await conn.sendQsh({ command: `system \"${IBMiClient.escapeForShell(command)}\"`, directory: cwd });
|
|
1129
|
+
}
|
|
1130
|
+
log("info", "audit.cl.run", { connectionName: args?.connectionName || ctx.activeName, environment });
|
|
1131
|
+
return json(cmdResult);
|
|
1132
|
+
}
|
|
1133
|
+
case "ibmi.diagnostics.parseEvfevent": {
|
|
1134
|
+
return json(parseEvfevent(String(args.content)));
|
|
1135
|
+
}
|
|
1136
|
+
case "ibmi.joblog.get": {
|
|
1137
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1138
|
+
const limit = clampNumber(args?.limit, 200, 1, 5000);
|
|
1139
|
+
const rows = await conn.runSQL(`select message_id, message_text, severity, message_timestamp from table(qsys2.joblog_info('*')) order by message_timestamp desc fetch first ${limit} rows only`);
|
|
1140
|
+
return json(rows);
|
|
1141
|
+
}
|
|
1142
|
+
case "ibmi.spool.list": {
|
|
1143
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1144
|
+
const limit = clampNumber(args?.limit, 200, 1, 5000);
|
|
1145
|
+
const rows = await conn.runSQL(`select job_name, spooled_file_name, spooled_file_number, output_queue_name, total_pages, file_status from qsys2.output_queue_entries fetch first ${limit} rows only`);
|
|
1146
|
+
return json(rows);
|
|
1147
|
+
}
|
|
1148
|
+
case "ibmi.spool.read": {
|
|
1149
|
+
const conn = ctx.ensureActive(args?.connectionName);
|
|
1150
|
+
const limit = clampNumber(args?.limit, 200, 1, 5000);
|
|
1151
|
+
const jobName = Tools.sqlString(String(args.jobName));
|
|
1152
|
+
const fileName = Tools.sqlString(String(args.spooledFileName));
|
|
1153
|
+
const fileNbr = clampNumber(args.spooledFileNumber, 1, 1, 999999999);
|
|
1154
|
+
const rows = await conn.runSQL(`select line_number, spooled_data from table(qsys2.display_spooled_file_data(${jobName}, ${fileName}, ${fileNbr})) fetch first ${limit} rows only`);
|
|
1155
|
+
return json(rows);
|
|
1156
|
+
}
|
|
1157
|
+
case "ibmi.tn5250.connect":
|
|
1158
|
+
case "ibmi.tn5250.readScreen":
|
|
1159
|
+
case "ibmi.tn5250.setField":
|
|
1160
|
+
case "ibmi.tn5250.sendKeys":
|
|
1161
|
+
case "ibmi.tn5250.waitFor":
|
|
1162
|
+
case "ibmi.tn5250.snapshot":
|
|
1163
|
+
case "ibmi.tn5250.disconnect": {
|
|
1164
|
+
throw new Error("TN5250 engine is not implemented yet in this build.");
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1168
|
+
}
|
|
1169
|
+
async function listActions(ctx) {
|
|
1170
|
+
const builtins = Object.values(LocalLanguageActions).flat();
|
|
1171
|
+
const custom = await ctx.store.listActions();
|
|
1172
|
+
return [...builtins, ...custom];
|
|
1173
|
+
}
|
|
1174
|
+
async function resolveAction(ctx, args) {
|
|
1175
|
+
if (args.action)
|
|
1176
|
+
return args.action;
|
|
1177
|
+
if (!args.actionName)
|
|
1178
|
+
return undefined;
|
|
1179
|
+
const actions = await listActions(ctx);
|
|
1180
|
+
return actions.find(a => a.name === args.actionName);
|
|
1181
|
+
}
|
|
1182
|
+
function rejectCredentialArgs(args, toolName) {
|
|
1183
|
+
if (!args || typeof args !== "object")
|
|
1184
|
+
return;
|
|
1185
|
+
if (typeof args.password === "string" && args.password.length > 0) {
|
|
1186
|
+
throw new Error(`${toolName} does not accept password. Use the local control-plane UI to set credentials securely.`);
|
|
1187
|
+
}
|
|
1188
|
+
if (typeof args.storePassword !== "undefined") {
|
|
1189
|
+
throw new Error(`${toolName} does not accept storePassword. Credentials must be managed via local control-plane UI.`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
function result(text) {
|
|
1193
|
+
return { content: [{ type: "text", text }] };
|
|
1194
|
+
}
|
|
1195
|
+
function json(obj) {
|
|
1196
|
+
return { content: [{ type: "text", text: JSON.stringify(obj, null, 2) }] };
|
|
1197
|
+
}
|
|
1198
|
+
function getConnectionName(ctx, args) {
|
|
1199
|
+
const name = args?.connectionName || ctx.activeName;
|
|
1200
|
+
if (!name)
|
|
1201
|
+
throw new Error("connectionName is required (no active connection)");
|
|
1202
|
+
return name;
|
|
1203
|
+
}
|
|
1204
|
+
async function updateConnectionSettings(ctx, name, update) {
|
|
1205
|
+
const conn = await ctx.store.getConnection(name);
|
|
1206
|
+
if (!conn)
|
|
1207
|
+
throw new Error(`Connection ${name} not found`);
|
|
1208
|
+
conn.settings = { ...(conn.settings || {}), ...stripUndefined(update) };
|
|
1209
|
+
await ctx.store.upsertConnection(conn);
|
|
1210
|
+
try {
|
|
1211
|
+
const active = ctx.ensureActive(name);
|
|
1212
|
+
active.setConfig({ ...active.getConfig(), ...conn.settings });
|
|
1213
|
+
}
|
|
1214
|
+
catch {
|
|
1215
|
+
// No active session for this connection.
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
async function applyLibraryList(conn, libraryList, currentLibrary) {
|
|
1219
|
+
if (libraryList && libraryList.length > 0) {
|
|
1220
|
+
const normalized = libraryList.map((l) => String(l).toUpperCase());
|
|
1221
|
+
const invalid = normalized.find((l) => !conn.validQsysName(l));
|
|
1222
|
+
if (invalid)
|
|
1223
|
+
throw new Error(`Invalid library name: ${invalid}`);
|
|
1224
|
+
const libs = normalized.join(" ");
|
|
1225
|
+
await conn.sendQsh({ command: `system \"CHGLIBL LIBL(${libs})\"` });
|
|
1226
|
+
}
|
|
1227
|
+
if (currentLibrary) {
|
|
1228
|
+
const lib = String(currentLibrary).toUpperCase();
|
|
1229
|
+
if (!conn.validQsysName(lib))
|
|
1230
|
+
throw new Error(`Invalid current library: ${lib}`);
|
|
1231
|
+
await conn.sendQsh({ command: `system \"CHGCURLIB ${lib}\"` });
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
function stripUndefined(obj) {
|
|
1235
|
+
const out = {};
|
|
1236
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1237
|
+
if (v !== undefined)
|
|
1238
|
+
out[k] = v;
|
|
1239
|
+
}
|
|
1240
|
+
return out;
|
|
1241
|
+
}
|
|
1242
|
+
async function listLocalFiles(root) {
|
|
1243
|
+
const results = [];
|
|
1244
|
+
async function walk(dir) {
|
|
1245
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1246
|
+
for (const entry of entries) {
|
|
1247
|
+
const full = path.join(dir, entry.name);
|
|
1248
|
+
const rel = path.relative(root, full).replace(/\\/g, "/");
|
|
1249
|
+
if (entry.isDirectory()) {
|
|
1250
|
+
await walk(full);
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
results.push(rel);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
await walk(root);
|
|
1258
|
+
return results;
|
|
1259
|
+
}
|
|
1260
|
+
async function listRemoteFiles(conn, remoteRoot) {
|
|
1261
|
+
const find = conn.remoteFeatures.find || "/QOpenSys/pkgs/bin/find";
|
|
1262
|
+
const res = await conn.sendCommand({ command: `${find} ${Tools.escapePath(remoteRoot)} -type f -print` });
|
|
1263
|
+
const lines = res.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1264
|
+
return lines.map((line) => line.replace(remoteRoot.replace(/\/$/, ""), "").replace(/^[\\/]/, ""));
|
|
1265
|
+
}
|
|
1266
|
+
function clampNumber(value, fallback, min, max) {
|
|
1267
|
+
const n = Number(value);
|
|
1268
|
+
if (!Number.isFinite(n))
|
|
1269
|
+
return fallback;
|
|
1270
|
+
return Math.max(min, Math.min(max, Math.floor(n)));
|
|
1271
|
+
}
|
|
1272
|
+
function isReadOnlySql(sql) {
|
|
1273
|
+
const stripped = sql
|
|
1274
|
+
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
|
1275
|
+
.split("\n")
|
|
1276
|
+
.filter(line => !line.trim().startsWith("--"))
|
|
1277
|
+
.join(" ")
|
|
1278
|
+
.trim()
|
|
1279
|
+
.toLowerCase();
|
|
1280
|
+
return stripped.startsWith("select ") || stripped.startsWith("with ") || stripped.startsWith("values ");
|
|
1281
|
+
}
|
|
1282
|
+
function enforceWritable(ctx, conn, operation, args) {
|
|
1283
|
+
if (conn.getConfig().readOnlyMode)
|
|
1284
|
+
throw new Error("Connection is in read-only mode");
|
|
1285
|
+
enforcePolicyOperation(ctx, operation, args);
|
|
1286
|
+
}
|
|
1287
|
+
function enforcePolicyOperation(ctx, operation, args) {
|
|
1288
|
+
const policy = ctx.getPolicy(args?.connectionName);
|
|
1289
|
+
if (policy.profile === "read-only") {
|
|
1290
|
+
throw new Error(`Blocked by policy (${policy.profile}): ${operation}`);
|
|
1291
|
+
}
|
|
1292
|
+
if (isDeniedByPolicy(policy, operation, args)) {
|
|
1293
|
+
throw new Error(`Blocked by policy: ${operation}`);
|
|
1294
|
+
}
|
|
1295
|
+
if (requiresApproval(policy, operation) && !Boolean(args?.approve)) {
|
|
1296
|
+
throw new Error(`Operation '${operation}' requires approve=true under '${policy.profile}' policy.`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
function requiresApproval(policy, operation) {
|
|
1300
|
+
if (policy.profile !== "guarded")
|
|
1301
|
+
return false;
|
|
1302
|
+
const requires = policy.requireApprovalFor || [];
|
|
1303
|
+
return requires.includes(operation);
|
|
1304
|
+
}
|
|
1305
|
+
function isDeniedByPolicy(policy, operation, args) {
|
|
1306
|
+
const denyCommands = (policy.denyCommands || []).map(v => v.toLowerCase());
|
|
1307
|
+
if (operation === "cl.run" && args?.command) {
|
|
1308
|
+
const command = String(args.command).toLowerCase();
|
|
1309
|
+
if (denyCommands.some(prefix => command.startsWith(prefix)))
|
|
1310
|
+
return true;
|
|
1311
|
+
}
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
function parseEvfevent(content) {
|
|
1315
|
+
const diagnostics = [];
|
|
1316
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
1317
|
+
const line = rawLine.trimEnd();
|
|
1318
|
+
if (!line.trim())
|
|
1319
|
+
continue;
|
|
1320
|
+
const match = line.match(/^\s*(\d+)\s+(\d+)\s+([A-Z0-9]+)\s+(INFO|WARN|ERROR)?\s*(.*)$/i);
|
|
1321
|
+
if (match) {
|
|
1322
|
+
diagnostics.push({
|
|
1323
|
+
line: Number(match[1]),
|
|
1324
|
+
column: Number(match[2]),
|
|
1325
|
+
code: match[3],
|
|
1326
|
+
severity: (match[4] || "ERROR").toUpperCase(),
|
|
1327
|
+
message: match[5] || line
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
diagnostics.push({ message: line });
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return diagnostics;
|
|
1335
|
+
}
|