mcp-for-i 0.1.13 → 0.2.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 (67) hide show
  1. package/README.md +17 -0
  2. package/dist/config/store.js +229 -109
  3. package/dist/controlplane/auth.js +128 -0
  4. package/dist/controlplane/clientIntegrationService.js +83 -4
  5. package/dist/controlplane/commands.js +14 -9
  6. package/dist/controlplane/connectionService.js +110 -5
  7. package/dist/controlplane/connectionTest.js +329 -0
  8. package/dist/controlplane/errors.js +12 -0
  9. package/dist/controlplane/runtimeService.js +98 -26
  10. package/dist/controlplane/server.js +122 -11
  11. package/dist/controlplane/serverShared.js +25 -6
  12. package/dist/controlplane/serviceManager.js +113 -100
  13. package/dist/controlplane/ui.js +1742 -920
  14. package/dist/ibmi/CompileTools.js +29 -1
  15. package/dist/ibmi/IBMiContent.js +435 -63
  16. package/dist/ibmi/IbmDocs.js +685 -0
  17. package/dist/ibmi/LocalLanguageActions.js +129 -3
  18. package/dist/ibmi/Search.js +158 -52
  19. package/dist/ibmi/Tools.js +7 -10
  20. package/dist/ibmi/client.js +220 -31
  21. package/dist/ibmi/evfevent.js +256 -0
  22. package/dist/ibmi/knownHosts.js +190 -0
  23. package/dist/ibmi/libraryList.js +93 -0
  24. package/dist/ibmi/sourceInspect.js +815 -0
  25. package/dist/ibmi/sqlJob.js +888 -0
  26. package/dist/ibmi/sqlParse.js +317 -0
  27. package/dist/ibmi/tn5250/certTrust.js +59 -0
  28. package/dist/ibmi/tn5250/client.js +310 -0
  29. package/dist/ibmi/tn5250/datastream.js +109 -0
  30. package/dist/ibmi/tn5250/ebcdic.js +74 -0
  31. package/dist/ibmi/tn5250/index.js +6 -0
  32. package/dist/ibmi/tn5250/screen.js +827 -0
  33. package/dist/ibmi/tn5250/telnet.js +186 -0
  34. package/dist/ibmi/variables.js +18 -7
  35. package/dist/index.js +43 -22
  36. package/dist/mcp/annotations.js +155 -0
  37. package/dist/mcp/audit.js +490 -133
  38. package/dist/mcp/confirm.js +48 -0
  39. package/dist/mcp/context.js +43 -5
  40. package/dist/mcp/deployMethods.js +192 -0
  41. package/dist/mcp/logger.js +5 -10
  42. package/dist/mcp/modules/bulkMembers.js +476 -0
  43. package/dist/mcp/modules/changedLines.js +189 -0
  44. package/dist/mcp/modules/compare.js +198 -0
  45. package/dist/mcp/modules/compile.js +515 -0
  46. package/dist/mcp/modules/ifsOps.js +123 -0
  47. package/dist/mcp/modules/index.js +38 -0
  48. package/dist/mcp/modules/inspect.js +811 -0
  49. package/dist/mcp/modules/libl.js +246 -0
  50. package/dist/mcp/modules/members.js +171 -0
  51. package/dist/mcp/modules/objects.js +339 -0
  52. package/dist/mcp/modules/system.js +266 -0
  53. package/dist/mcp/modules/tn5250.js +515 -0
  54. package/dist/mcp/policy.js +412 -0
  55. package/dist/mcp/tools.js +1700 -698
  56. package/dist/security/credentialStore.js +62 -13
  57. package/dist/security/localPaths.js +138 -0
  58. package/dist/security/redact.js +79 -0
  59. package/dist/util/atomicWrite.js +101 -0
  60. package/dist/util/fileLock.js +206 -0
  61. package/docs/capabilities.md +50 -7
  62. package/docs/configuration.md +189 -60
  63. package/docs/examples.md +109 -73
  64. package/docs/overview.md +15 -15
  65. package/docs/security.md +101 -16
  66. package/docs/tools.md +363 -188
  67. package/package.json +56 -53
@@ -0,0 +1,246 @@
1
+ import { applyLibraryList, enforceWritable, getConnectionName, json, updateConnectionSettings } from "../tools.js";
2
+ const WRITE_ANNOTATIONS = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
3
+ async function storedLibraryList(ctx, name, active) {
4
+ const stored = await ctx.store.getConnection(name);
5
+ if (!stored)
6
+ throw new Error(`Connection ${name} not found`);
7
+ let config = {};
8
+ try {
9
+ config = active.getConfig() || {};
10
+ }
11
+ catch {
12
+ config = {};
13
+ }
14
+ const list = (stored.settings?.libraryList ?? config.libraryList ?? []).map((lib) => String(lib).toUpperCase());
15
+ const currentLibrary = stored.settings?.currentLibrary ?? config.currentLibrary;
16
+ return { stored, list, currentLibrary };
17
+ }
18
+ /** Returns a copy of `list` with `library` moved; positions are 1-based. */
19
+ export function moveLibrary(list, library, options) {
20
+ const index = list.indexOf(library);
21
+ if (index < 0)
22
+ throw new Error(`${library} is not in the library list`);
23
+ const hasPosition = options.position !== undefined && options.position !== null;
24
+ if (hasPosition === Boolean(options.direction))
25
+ throw new Error("Give either position or direction");
26
+ let target;
27
+ if (hasPosition) {
28
+ const position = Number(options.position);
29
+ if (!Number.isInteger(position) || position < 1)
30
+ throw new Error("position must be a whole number from 1");
31
+ target = Math.min(position, list.length) - 1;
32
+ }
33
+ else {
34
+ const steps = options.steps === undefined ? 1 : Number(options.steps);
35
+ if (!Number.isInteger(steps) || steps < 1)
36
+ throw new Error("steps must be a whole number from 1");
37
+ switch (options.direction) {
38
+ case "up":
39
+ target = Math.max(0, index - steps);
40
+ break;
41
+ case "down":
42
+ target = Math.min(list.length - 1, index + steps);
43
+ break;
44
+ case "top":
45
+ target = 0;
46
+ break;
47
+ case "bottom":
48
+ target = list.length - 1;
49
+ break;
50
+ default: throw new Error("direction must be up, down, top or bottom");
51
+ }
52
+ }
53
+ const next = list.filter((_lib, i) => i !== index);
54
+ next.splice(target, 0, library);
55
+ return next;
56
+ }
57
+ function requireLibraryName(conn, value, label = "library") {
58
+ const lib = String(value ?? "").trim().toUpperCase();
59
+ if (!lib)
60
+ throw new Error(`${label} is required`);
61
+ if (!conn.validQsysName(lib))
62
+ throw new Error(`Invalid ${label}: ${lib}`);
63
+ return lib;
64
+ }
65
+ async function move(ctx, args) {
66
+ const name = getConnectionName(ctx, args);
67
+ const active = await ctx.ensureActive(name);
68
+ const library = requireLibraryName(active, args?.library);
69
+ const { list, currentLibrary } = await storedLibraryList(ctx, name, active);
70
+ const next = moveLibrary(list, library, { position: args?.position, direction: args?.direction, steps: args?.steps });
71
+ await enforceWritable(ctx, active, "qsys.write", args);
72
+ await updateConnectionSettings(ctx, name, { libraryList: next });
73
+ const applied = args?.applyToJob ? await applyLibraryList(active, next, currentLibrary) : undefined;
74
+ return json({
75
+ library,
76
+ from: list.indexOf(library) + 1,
77
+ to: next.indexOf(library) + 1,
78
+ libraryList: next,
79
+ ...(applied?.warning ? { warning: applied.warning } : {})
80
+ });
81
+ }
82
+ /** Libraries in `names` that exist, checked with QSYS2.OBJECT_STATISTICS. */
83
+ export async function existingLibraries(conn, names) {
84
+ if (names.length === 0)
85
+ return new Set();
86
+ const inList = names.map(lib => `'${lib.replace(/'/g, "''")}'`).join(", ");
87
+ let rows;
88
+ try {
89
+ rows = await conn.runSQL(`select OBJNAME from table(QSYS2.OBJECT_STATISTICS('QSYS', '*LIB', '*ALLSIMPLE')) X where OBJNAME in (${inList})`);
90
+ }
91
+ catch {
92
+ // Older releases do not know *ALLSIMPLE.
93
+ rows = await conn.runSQL(`select OBJNAME from table(QSYS2.OBJECT_STATISTICS('QSYS', '*LIB')) X where OBJNAME in (${inList})`);
94
+ }
95
+ return new Set(rows.map((row) => String(row.OBJNAME ?? "").trim().toUpperCase()));
96
+ }
97
+ async function prune(ctx, args) {
98
+ const name = getConnectionName(ctx, args);
99
+ const active = await ctx.ensureActive(name);
100
+ const { list, currentLibrary } = await storedLibraryList(ctx, name, active);
101
+ const valid = list.filter(lib => active.validQsysName(lib));
102
+ const invalidNames = list.filter(lib => !active.validQsysName(lib));
103
+ const checkNames = [...new Set([...valid, ...(currentLibrary ? [String(currentLibrary).toUpperCase()] : [])])]
104
+ .filter(lib => active.validQsysName(lib));
105
+ const existing = await existingLibraries(active, checkNames);
106
+ const removed = [...list.filter(lib => !existing.has(lib))];
107
+ const kept = list.filter(lib => existing.has(lib));
108
+ const currentLibraryMissing = Boolean(currentLibrary) && !existing.has(String(currentLibrary).toUpperCase());
109
+ const dryRun = Boolean(args?.dryRun);
110
+ let applied;
111
+ if (!dryRun && removed.length > 0) {
112
+ await enforceWritable(ctx, active, "qsys.write", args);
113
+ await updateConnectionSettings(ctx, name, { libraryList: kept });
114
+ if (args?.applyToJob)
115
+ applied = await applyLibraryList(active, kept, currentLibraryMissing ? undefined : currentLibrary);
116
+ }
117
+ return json({
118
+ dryRun,
119
+ removed,
120
+ ...(invalidNames.length ? { invalidNames } : {}),
121
+ libraryList: dryRun ? list : kept,
122
+ ...(dryRun ? { wouldBecome: kept } : {}),
123
+ currentLibrary: currentLibrary ?? null,
124
+ ...(currentLibraryMissing ? { warning: `Current library ${currentLibrary} does not exist; it was left unchanged. Use ibmi.libl.setCurrent.` } : {}),
125
+ ...(applied?.warning ? { applyWarning: applied.warning } : {})
126
+ });
127
+ }
128
+ /** Parses QSYS2.LIBRARY_LIST_INFO rows into the user portion and the current library. */
129
+ export function libraryListFromRows(rows) {
130
+ const libraryList = [];
131
+ let currentLibrary;
132
+ for (const row of rows) {
133
+ const lib = String(row.SYSTEM_SCHEMA_NAME ?? "").trim().toUpperCase();
134
+ const type = String(row.TYPE ?? "").trim().toUpperCase();
135
+ if (!lib)
136
+ continue;
137
+ if (type === "USER" && !libraryList.includes(lib))
138
+ libraryList.push(lib);
139
+ if (type === "CURRENT")
140
+ currentLibrary = lib;
141
+ }
142
+ return { libraryList, currentLibrary };
143
+ }
144
+ async function runProfileCommand(ctx, args) {
145
+ const name = getConnectionName(ctx, args);
146
+ const active = await ctx.ensureActive(name);
147
+ const stored = await ctx.store.getConnection(name);
148
+ if (!stored)
149
+ throw new Error(`Connection ${name} not found`);
150
+ const profileName = args?.profileName ? String(args.profileName) : stored.currentProfile;
151
+ if (!profileName)
152
+ throw new Error("No active profile; pass profileName or activate a profile first");
153
+ const profile = (stored.profiles || []).find(item => item.name === profileName);
154
+ if (!profile)
155
+ throw new Error(`Profile ${profileName} not found`);
156
+ const command = String(profile.setLibraryListCommand || "").trim();
157
+ if (!command)
158
+ throw new Error(`Profile ${profileName} has no setLibraryListCommand`);
159
+ if (command.startsWith("?") || /[\r\n]/.test(command))
160
+ throw new Error("setLibraryListCommand must be a single CL command without prompting");
161
+ // The command runs as CL, so CL command policy applies; storing the result is a settings write.
162
+ await enforceWritable(ctx, active, "cl.run", { ...args, command });
163
+ await enforceWritable(ctx, active, "qsys.write", args);
164
+ // Run the command and read the resulting library list in the same database job.
165
+ const rows = await active.runSQL(`CALL QSYS2.QCMDEXC('${command.replace(/'/g, "''")}');\n` +
166
+ "select ORDINAL_POSITION, SYSTEM_SCHEMA_NAME, TYPE from QSYS2.LIBRARY_LIST_INFO order by ORDINAL_POSITION");
167
+ const { libraryList, currentLibrary } = libraryListFromRows(rows);
168
+ if (libraryList.length === 0 && !currentLibrary) {
169
+ throw new Error("The command ran but no library list could be read back from QSYS2.LIBRARY_LIST_INFO");
170
+ }
171
+ await updateConnectionSettings(ctx, name, { libraryList, currentLibrary });
172
+ if (args?.saveToProfile) {
173
+ await ctx.store.saveProfile(name, { ...profile, libraryList, ...(currentLibrary ? { currentLibrary } : {}) });
174
+ }
175
+ const applied = args?.applyToJob ? await applyLibraryList(active, libraryList, currentLibrary) : undefined;
176
+ return json({
177
+ profile: profileName,
178
+ command,
179
+ libraryList,
180
+ currentLibrary: currentLibrary ?? null,
181
+ savedToProfile: Boolean(args?.saveToProfile),
182
+ ...(applied?.warning ? { warning: applied.warning } : {})
183
+ });
184
+ }
185
+ const COMMON_PROPERTIES = {
186
+ connectionName: { type: "string", description: "Saved IBM i connection name." },
187
+ applyToJob: { type: "boolean", default: false, description: "Also run CHGLIBL/CHGCURLIB now." },
188
+ approve: { type: "boolean", description: "Set true to approve guarded-policy operations." }
189
+ };
190
+ export const liblModule = {
191
+ tools: [
192
+ {
193
+ name: "ibmi.libl.move",
194
+ description: "Move a library within the connection's library list: to a 1-based position, or up/down by steps, or to the top/bottom.",
195
+ inputSchema: {
196
+ type: "object",
197
+ properties: {
198
+ ...COMMON_PROPERTIES,
199
+ library: { type: "string" },
200
+ position: { type: "number", description: "New 1-based position." },
201
+ direction: { type: "string", enum: ["up", "down", "top", "bottom"] },
202
+ steps: { type: "number", description: "How far to move up or down (default 1)." }
203
+ },
204
+ required: ["library"],
205
+ additionalProperties: false
206
+ },
207
+ annotations: WRITE_ANNOTATIONS
208
+ },
209
+ {
210
+ name: "ibmi.libl.prune",
211
+ description: "Remove libraries that no longer exist from the connection's library list (checked with QSYS2.OBJECT_STATISTICS). dryRun only reports.",
212
+ inputSchema: {
213
+ type: "object",
214
+ properties: {
215
+ ...COMMON_PROPERTIES,
216
+ dryRun: { type: "boolean", default: false }
217
+ },
218
+ additionalProperties: false
219
+ },
220
+ annotations: { ...WRITE_ANNOTATIONS, idempotentHint: true }
221
+ },
222
+ {
223
+ name: "ibmi.libl.runProfileCommand",
224
+ description: "Run a profile's setLibraryListCommand (the active profile by default) and store the resulting library list and current " +
225
+ "library in the connection settings. The command runs as CL, so CL command policy applies.",
226
+ inputSchema: {
227
+ type: "object",
228
+ properties: {
229
+ ...COMMON_PROPERTIES,
230
+ profileName: { type: "string", description: "Profile to use (default: the active profile)." },
231
+ saveToProfile: { type: "boolean", default: false, description: "Also save the result into the profile." }
232
+ },
233
+ additionalProperties: false
234
+ },
235
+ annotations: WRITE_ANNOTATIONS
236
+ }
237
+ ],
238
+ handle: async (ctx, name, args) => {
239
+ switch (name) {
240
+ case "ibmi.libl.move": return move(ctx, args);
241
+ case "ibmi.libl.prune": return prune(ctx, args);
242
+ case "ibmi.libl.runProfileCommand": return runProfileCommand(ctx, args);
243
+ }
244
+ throw new Error(`Unknown tool: ${name}`);
245
+ }
246
+ };
@@ -0,0 +1,171 @@
1
+ import { Tools } from "../../ibmi/Tools.js";
2
+ import { commandOutcome, enforceWritable, toolError } from "../tools.js";
3
+ import { clTextValue, enforceLibraryPolicy, optionalName, refuseProtectedLibrary, requireName, runCheckedCl } from "./objects.js";
4
+ /**
5
+ * Source member copy and attribute changes. Each tool passes the write gate,
6
+ * reports a failing CL command as a tool error, and drops the cached member
7
+ * snapshot for every member it may have changed.
8
+ */
9
+ const CONNECTION_NAME = { type: "string", description: "Saved IBM i connection name." };
10
+ const APPROVE = { type: "boolean", description: "Set true to approve guarded-policy operations." };
11
+ function annotations(destructive, idempotent) {
12
+ return { readOnlyHint: false, destructiveHint: destructive, idempotentHint: idempotent, openWorldHint: false };
13
+ }
14
+ const MEMBER_PROPERTIES = {
15
+ library: { type: "string" },
16
+ sourceFile: { type: "string" },
17
+ member: { type: "string" }
18
+ };
19
+ const tools = [
20
+ {
21
+ name: "ibmi.qsys.members.copy",
22
+ description: "Copy a source member with CPYSRCF. The target defaults to the same library, source file and member name; " +
23
+ "at least one must differ. An existing target member is only replaced when replace=true.",
24
+ inputSchema: {
25
+ type: "object",
26
+ properties: {
27
+ ...MEMBER_PROPERTIES,
28
+ toLibrary: { type: "string" },
29
+ toSourceFile: { type: "string" },
30
+ toMember: { type: "string" },
31
+ replace: { type: "boolean", description: "Replace the target member if it exists. Default false." },
32
+ connectionName: CONNECTION_NAME,
33
+ approve: APPROVE
34
+ },
35
+ required: ["library", "sourceFile", "member"],
36
+ additionalProperties: false
37
+ },
38
+ annotations: annotations(true, false)
39
+ },
40
+ {
41
+ name: "ibmi.qsys.members.changeText",
42
+ description: "Change a member's text description with CHGPFM TEXT(...). An empty text sets *BLANK.",
43
+ inputSchema: {
44
+ type: "object",
45
+ properties: {
46
+ ...MEMBER_PROPERTIES,
47
+ text: { type: "string", description: "Up to 50 characters." },
48
+ connectionName: CONNECTION_NAME,
49
+ approve: APPROVE
50
+ },
51
+ required: ["library", "sourceFile", "member", "text"],
52
+ additionalProperties: false
53
+ },
54
+ annotations: annotations(false, true)
55
+ },
56
+ {
57
+ name: "ibmi.qsys.members.changeType",
58
+ description: "Change a member's source type with CHGPFM SRCTYPE(...), for example RPGLE or SQLRPGLE.",
59
+ inputSchema: {
60
+ type: "object",
61
+ properties: {
62
+ ...MEMBER_PROPERTIES,
63
+ srctype: { type: "string", description: "New source type (up to 10 characters) or *NONE." },
64
+ connectionName: CONNECTION_NAME,
65
+ approve: APPROVE
66
+ },
67
+ required: ["library", "sourceFile", "member", "srctype"],
68
+ additionalProperties: false
69
+ },
70
+ annotations: annotations(false, true)
71
+ }
72
+ ];
73
+ export function requireSourceType(value) {
74
+ const type = String(value ?? "").trim().toUpperCase();
75
+ if (!type)
76
+ throw new Error("srctype is required");
77
+ if (type !== "*NONE" && !/^[A-Z0-9#@$_.]{1,10}$/.test(type))
78
+ throw new Error(`Invalid srctype: ${value}`);
79
+ return type;
80
+ }
81
+ function invalidate(conn, library, sourceFile, member) {
82
+ if (typeof conn?.content?.invalidateMemberSnapshot === "function") {
83
+ conn.content.invalidateMemberSnapshot(library, sourceFile, member);
84
+ }
85
+ }
86
+ async function memberExists(conn, library, sourceFile, member) {
87
+ const rows = await conn.runSQL(`select SYSTEM_TABLE_MEMBER from QSYS2.SYSPARTITIONSTAT where SYSTEM_TABLE_SCHEMA = ${Tools.sqlString(library)} ` +
88
+ `and SYSTEM_TABLE_NAME = ${Tools.sqlString(sourceFile)} and SYSTEM_TABLE_MEMBER = ${Tools.sqlString(member)} ` +
89
+ `fetch first 1 rows only`);
90
+ return Array.isArray(rows) && rows.length > 0;
91
+ }
92
+ async function memberAttributes(conn, library, sourceFile, member) {
93
+ const rows = await conn.runSQL(`select SOURCE_TYPE, PARTITION_TEXT from QSYS2.SYSPARTITIONSTAT where SYSTEM_TABLE_SCHEMA = ${Tools.sqlString(library)} ` +
94
+ `and SYSTEM_TABLE_NAME = ${Tools.sqlString(sourceFile)} and SYSTEM_TABLE_MEMBER = ${Tools.sqlString(member)} ` +
95
+ `fetch first 1 rows only`).catch(() => []);
96
+ const row = Array.isArray(rows) && rows[0] ? rows[0] : {};
97
+ const srctype = String(row.SOURCE_TYPE ?? "").trim().toUpperCase();
98
+ return {
99
+ srctype: /^[A-Z0-9_]{1,10}$/.test(srctype) ? srctype : "",
100
+ text: String(row.PARTITION_TEXT ?? "").trim()
101
+ };
102
+ }
103
+ async function handle(ctx, name, args) {
104
+ args = args || {};
105
+ const conn = await ctx.ensureActive(args.connectionName);
106
+ await enforceWritable(ctx, conn, "qsys.write", args);
107
+ const library = requireName(args.library, "library");
108
+ const sourceFile = requireName(args.sourceFile, "sourceFile");
109
+ const member = requireName(args.member, "member");
110
+ switch (name) {
111
+ case "ibmi.qsys.members.copy": {
112
+ const toLibrary = optionalName(args.toLibrary, "toLibrary", library);
113
+ const toSourceFile = optionalName(args.toSourceFile, "toSourceFile", sourceFile);
114
+ const toMember = optionalName(args.toMember, "toMember", member);
115
+ if (toLibrary === library && toSourceFile === sourceFile && toMember === member) {
116
+ throw new Error("Copy target is the same as the source: set toLibrary, toSourceFile or toMember");
117
+ }
118
+ refuseProtectedLibrary(toLibrary, "copying members");
119
+ enforceLibraryPolicy(ctx, name, args, [library, toLibrary]);
120
+ if (args.replace !== true && await memberExists(conn, toLibrary, toSourceFile, toMember)) {
121
+ return toolError(`Member ${toLibrary}/${toSourceFile}(${toMember}) already exists. Pass replace=true to overwrite it.`, { library: toLibrary, sourceFile: toSourceFile, member: toMember });
122
+ }
123
+ const command = `CPYSRCF FROMFILE(${library}/${sourceFile}) TOFILE(${toLibrary}/${toSourceFile}) ` +
124
+ `FROMMBR(${member}) TOMBR(${toMember}) MBROPT(*REPLACE)`;
125
+ try {
126
+ const copied = await runCheckedCl(ctx, conn, args, command);
127
+ if (copied.code === 0) {
128
+ // CPYSRCF does not carry the source type or text to a new member.
129
+ const attributes = await memberAttributes(conn, library, sourceFile, member);
130
+ if (attributes.srctype || attributes.text) {
131
+ const change = `CHGPFM FILE(${toLibrary}/${toSourceFile}) MBR(${toMember})` +
132
+ (attributes.srctype ? ` SRCTYPE(${attributes.srctype})` : "") +
133
+ (attributes.text ? ` TEXT(${clTextValue(attributes.text)})` : "");
134
+ const changed = await runCheckedCl(ctx, conn, args, change);
135
+ if (changed.code !== 0)
136
+ return commandOutcome(changed);
137
+ }
138
+ }
139
+ return commandOutcome(copied);
140
+ }
141
+ finally {
142
+ invalidate(conn, toLibrary, toSourceFile, toMember);
143
+ }
144
+ }
145
+ case "ibmi.qsys.members.changeText": {
146
+ const text = clTextValue(args.text);
147
+ refuseProtectedLibrary(library, "changing members");
148
+ enforceLibraryPolicy(ctx, name, args, [library]);
149
+ try {
150
+ return commandOutcome(await runCheckedCl(ctx, conn, args, `CHGPFM FILE(${library}/${sourceFile}) MBR(${member}) TEXT(${text})`));
151
+ }
152
+ finally {
153
+ invalidate(conn, library, sourceFile, member);
154
+ }
155
+ }
156
+ case "ibmi.qsys.members.changeType": {
157
+ const srctype = requireSourceType(args.srctype);
158
+ refuseProtectedLibrary(library, "changing members");
159
+ enforceLibraryPolicy(ctx, name, args, [library]);
160
+ try {
161
+ return commandOutcome(await runCheckedCl(ctx, conn, args, `CHGPFM FILE(${library}/${sourceFile}) MBR(${member}) SRCTYPE(${srctype})`));
162
+ }
163
+ finally {
164
+ invalidate(conn, library, sourceFile, member);
165
+ }
166
+ }
167
+ default:
168
+ throw new Error(`Unknown tool: ${name}`);
169
+ }
170
+ }
171
+ export const membersModule = { tools, handle };