@sfmc-bds/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/commands-behavior-pack.d.ts +46 -0
- package/dist/commands-behavior-pack.d.ts.map +1 -0
- package/dist/commands-behavior-pack.js +160 -0
- package/dist/commands-behavior-pack.js.map +1 -0
- package/dist/commands.d.ts +11 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +191 -0
- package/dist/commands.js.map +1 -0
- package/dist/dispatcher.d.ts +13 -0
- package/dist/dispatcher.d.ts.map +1 -0
- package/dist/dispatcher.js +23 -0
- package/dist/dispatcher.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/logs.d.ts +35 -0
- package/dist/logs.d.ts.map +1 -0
- package/dist/logs.js +202 -0
- package/dist/logs.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +189 -0
- package/dist/main.js.map +1 -0
- package/dist/module-commands.d.ts +39 -0
- package/dist/module-commands.d.ts.map +1 -0
- package/dist/module-commands.js +367 -0
- package/dist/module-commands.js.map +1 -0
- package/dist/registry.d.ts +53 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +125 -0
- package/dist/registry.js.map +1 -0
- package/dist/remote-agent.d.ts +18 -0
- package/dist/remote-agent.d.ts.map +1 -0
- package/dist/remote-agent.js +212 -0
- package/dist/remote-agent.js.map +1 -0
- package/dist/repl.d.ts +9 -0
- package/dist/repl.d.ts.map +1 -0
- package/dist/repl.js +652 -0
- package/dist/repl.js.map +1 -0
- package/dist/runtime.d.ts +28 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +74 -0
- package/dist/runtime.js.map +1 -0
- package/dist/services.d.ts +61 -0
- package/dist/services.d.ts.map +1 -0
- package/dist/services.js +259 -0
- package/dist/services.js.map +1 -0
- package/dist/terminal.d.ts +3 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +178 -0
- package/dist/terminal.js.map +1 -0
- package/dist/theme.d.ts +33 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +55 -0
- package/dist/theme.js.map +1 -0
- package/dist/wizard.d.ts +2 -0
- package/dist/wizard.d.ts.map +1 -0
- package/dist/wizard.js +475 -0
- package/dist/wizard.js.map +1 -0
- package/package.json +65 -0
package/dist/repl.js
ADDED
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
import process, { stdin, stdout } from "node:process";
|
|
2
|
+
import pkg from "../package.json" with { type: "json" };
|
|
3
|
+
import { cmdLogs, cmdRestart, cmdSend, cmdStart, cmdStartAll, cmdStatus, cmdStop, cmdStopAll, cmdUpdate } from "./commands.js";
|
|
4
|
+
import { formatLog, getAllLogs, onLog, wrapLogLine } from "./logs.js";
|
|
5
|
+
import { cmdModuleInfo, cmdModuleInstall, cmdModuleList, cmdModuleUninstall, cmdModuleVerify } from "./module-commands.js";
|
|
6
|
+
import { disableRemoteAgent, enrollRemoteAgent, remoteStatus, startRemoteAgent } from "./remote-agent.js";
|
|
7
|
+
import { forceStopAll, SERVICE_NAMES, stopAll } from "./services.js";
|
|
8
|
+
import { c } from "./theme.js";
|
|
9
|
+
function setRaw(v) {
|
|
10
|
+
try {
|
|
11
|
+
if (stdin.isTTY && typeof stdin.setRawMode === "function")
|
|
12
|
+
stdin.setRawMode(v);
|
|
13
|
+
}
|
|
14
|
+
catch { }
|
|
15
|
+
}
|
|
16
|
+
const welcome = `\n
|
|
17
|
+
${c.text(`⠪⡁⡯⠁`)}
|
|
18
|
+
${c.text(`⠒⠁⠃`)}${c.purple(`⠄`)}
|
|
19
|
+
${c.text(`⡷⡇⡎⠁`)} ${c.text(`S`)}${c.dim(`cripts`)} ${c.text(`F`)}${c.dim(`or`)} ${c.text(`M`)}${c.dim(`ine`)}${c.text(`c`)}${c.dim(`raft Server`)} v${pkg.version}
|
|
20
|
+
${c.text(`⠃⠃⠑⠂`)} ${c.dim(`help · Ctrl+L · ↹ · → · ↑↓`)}\n
|
|
21
|
+
`;
|
|
22
|
+
const version = `\n
|
|
23
|
+
${c.text(`⠪⡁⡯⠁`)}
|
|
24
|
+
${c.text(`⠒⠁⠃`)}${c.purple(`⠄`)}
|
|
25
|
+
${c.text(`⡷⡇⡎⠁`)} ${c.dim(`https://github.com/DogeLakeDev/ScriptsForMinecraftServer`)}
|
|
26
|
+
${c.text(`⠃⠃⠑⠂`)} ${c.text(`S`)}${c.dim(`cripts`)} ${c.text(`F`)}${c.dim(`or`)} ${c.text(`M`)}${c.dim(`ine`)}${c.text(`c`)}${c.dim(`raft Server`)} v${pkg.version}\n
|
|
27
|
+
`;
|
|
28
|
+
export const HELP = `
|
|
29
|
+
${c.bold("Commands")}
|
|
30
|
+
${c.green("status")} Show all services status
|
|
31
|
+
${c.green("logs")} <svc> [-n N] [-f] View / follow service logs
|
|
32
|
+
${c.green("start")} <svc>|-all Start (or all)
|
|
33
|
+
${c.green("stop")} <svc>|-all Stop (or all)
|
|
34
|
+
${c.green("restart")} <svc>|-all Restart (or all)
|
|
35
|
+
${c.green("send")} <svc> <msg> Send command to a service's stdin
|
|
36
|
+
${c.green("init")} Setup wizard
|
|
37
|
+
${c.green("update")} [--check-only] [--channel=release|preview]
|
|
38
|
+
Check/apply BDS update
|
|
39
|
+
${c.green("remote status")} Show remote-agent enrollment status
|
|
40
|
+
${c.green("remote enroll")} <url> <token> [name]
|
|
41
|
+
Enroll this supervisor with a controller
|
|
42
|
+
${c.green("remote disable")} Disable + disconnect remote agent
|
|
43
|
+
${c.green("module list")} [--source <id>]
|
|
44
|
+
List modules across marketplace sources
|
|
45
|
+
${c.green("module install")} <id> [--source <id>] [--no-verify]
|
|
46
|
+
Fetch + verify + install a module
|
|
47
|
+
${c.green("module uninstall")} <id> Remove an installed module
|
|
48
|
+
${c.green("module verify")} [id] Verify installed modules (SHA-256)
|
|
49
|
+
${c.green("module info")} <id> Show one module's details
|
|
50
|
+
${c.green("version")} Show version
|
|
51
|
+
${c.green("help")} Show this
|
|
52
|
+
${c.green("quit")} / ${c.green("exit")} Exit
|
|
53
|
+
|
|
54
|
+
${c.dim("Shortcuts:")}
|
|
55
|
+
${c.dim("Tab")} Complete (cycle on repeat)
|
|
56
|
+
${c.dim("→")} Accept gray suggestion
|
|
57
|
+
${c.dim("Ctrl+L")} Filter log level / source
|
|
58
|
+
${c.dim("↑↓")} History
|
|
59
|
+
`;
|
|
60
|
+
const COMMANDS = [
|
|
61
|
+
"status",
|
|
62
|
+
"logs",
|
|
63
|
+
"start",
|
|
64
|
+
"stop",
|
|
65
|
+
"restart",
|
|
66
|
+
"send",
|
|
67
|
+
"init",
|
|
68
|
+
"update",
|
|
69
|
+
"remote",
|
|
70
|
+
"module",
|
|
71
|
+
"version",
|
|
72
|
+
"help",
|
|
73
|
+
"quit",
|
|
74
|
+
"exit",
|
|
75
|
+
];
|
|
76
|
+
/**
|
|
77
|
+
* 解析当前输入行,提取命令名、参数位置、正在输入的 word。
|
|
78
|
+
* 末尾空格视为"刚结束一个 word,准备输入下一个"。
|
|
79
|
+
*/
|
|
80
|
+
function parseLine(line) {
|
|
81
|
+
const endsWithSpace = line.length > 0 && /\s$/.test(line);
|
|
82
|
+
const trimmed = line.trim();
|
|
83
|
+
if (!trimmed)
|
|
84
|
+
return { cmd: "", argIndex: 0, current: "" };
|
|
85
|
+
const tokens = trimmed.split(/\s+/);
|
|
86
|
+
if (endsWithSpace) {
|
|
87
|
+
return { cmd: tokens[0] ?? "", argIndex: tokens.length - 1, current: "" };
|
|
88
|
+
}
|
|
89
|
+
if (tokens.length === 1)
|
|
90
|
+
return { cmd: "", argIndex: 0, current: tokens[0] };
|
|
91
|
+
return { cmd: tokens[0], argIndex: tokens.length - 2, current: tokens[tokens.length - 1] };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 根据命令 + 参数位置返回补全候选 (区分命令,不再把服务名当成所有指令的二级参数)。
|
|
95
|
+
*/
|
|
96
|
+
function getCompletions(parsed) {
|
|
97
|
+
const { cmd, argIndex, current } = parsed;
|
|
98
|
+
const sw = (s) => s.startsWith(current);
|
|
99
|
+
if (!cmd)
|
|
100
|
+
return COMMANDS.filter(sw);
|
|
101
|
+
switch (cmd) {
|
|
102
|
+
case "logs":
|
|
103
|
+
case "log":
|
|
104
|
+
if (argIndex === 0)
|
|
105
|
+
return SERVICE_NAMES.filter(sw);
|
|
106
|
+
if (argIndex === 1)
|
|
107
|
+
return ["-n", "-f"].filter(sw);
|
|
108
|
+
return [];
|
|
109
|
+
case "start":
|
|
110
|
+
case "stop":
|
|
111
|
+
case "restart":
|
|
112
|
+
if (argIndex === 0)
|
|
113
|
+
return ["-all", ...SERVICE_NAMES].filter(sw);
|
|
114
|
+
return [];
|
|
115
|
+
case "send":
|
|
116
|
+
if (argIndex === 0)
|
|
117
|
+
return SERVICE_NAMES.filter(sw);
|
|
118
|
+
return [];
|
|
119
|
+
case "update":
|
|
120
|
+
return ["--check-only", "--force", "--channel=release", "--channel=preview"].filter(sw);
|
|
121
|
+
case "remote":
|
|
122
|
+
if (argIndex === 0)
|
|
123
|
+
return ["status", "enroll", "disable"].filter(sw);
|
|
124
|
+
return [];
|
|
125
|
+
case "module":
|
|
126
|
+
case "mod":
|
|
127
|
+
if (argIndex === 0)
|
|
128
|
+
return ["list", "install", "uninstall", "verify", "info"].filter(sw);
|
|
129
|
+
if (argIndex === 1) {
|
|
130
|
+
const verb = (parsed.cmd === "module" || parsed.cmd === "mod") ? "" : parsed.cmd;
|
|
131
|
+
if (["install", "list"].includes(verb))
|
|
132
|
+
return ["--source", "--no-verify"].filter(sw);
|
|
133
|
+
}
|
|
134
|
+
return [];
|
|
135
|
+
default:
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/* ==================================================================
|
|
140
|
+
* Escape sequence consumer
|
|
141
|
+
* ================================================================== */
|
|
142
|
+
function consumeEscapeSeq(chunk, i) {
|
|
143
|
+
if (chunk[i] !== 0x1b)
|
|
144
|
+
return null;
|
|
145
|
+
const rem = chunk.length - i - 1;
|
|
146
|
+
if (rem >= 2 && chunk[i + 1] === 0x5b) {
|
|
147
|
+
let j = i + 2;
|
|
148
|
+
while (j < chunk.length && chunk[j] >= 0x30 && chunk[j] <= 0x3f)
|
|
149
|
+
j++;
|
|
150
|
+
while (j < chunk.length && chunk[j] >= 0x20 && chunk[j] <= 0x2f)
|
|
151
|
+
j++;
|
|
152
|
+
if (j < chunk.length && chunk[j] >= 0x40 && chunk[j] <= 0x7e)
|
|
153
|
+
j++;
|
|
154
|
+
return j;
|
|
155
|
+
}
|
|
156
|
+
if (rem >= 2 && chunk[i + 1] === 0x4f)
|
|
157
|
+
return i + 3;
|
|
158
|
+
if (rem >= 1)
|
|
159
|
+
return i + 2;
|
|
160
|
+
return i + 1;
|
|
161
|
+
}
|
|
162
|
+
async function simpleSelect(items) {
|
|
163
|
+
const wasRaw = stdin.isRaw ?? false;
|
|
164
|
+
setRaw(true);
|
|
165
|
+
stdin.resume();
|
|
166
|
+
let selected = 0;
|
|
167
|
+
const h = Math.min(items.length, 8);
|
|
168
|
+
let lastLines = h;
|
|
169
|
+
function render(first) {
|
|
170
|
+
if (!first) {
|
|
171
|
+
stdout.write(`\x1B[${lastLines}A\x1B[J`);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
stdout.write("\x1B[J");
|
|
175
|
+
}
|
|
176
|
+
lastLines = h;
|
|
177
|
+
let out = "";
|
|
178
|
+
for (let i = 0; i < h; i++) {
|
|
179
|
+
const cur = i === selected ? `◉ ${c.text(items[i].label)}` : `○ ${c.text(items[i].label)}`;
|
|
180
|
+
out += `${cur}\n`;
|
|
181
|
+
}
|
|
182
|
+
stdout.write(out);
|
|
183
|
+
}
|
|
184
|
+
function clear() {
|
|
185
|
+
stdout.write(`\x1B[${lastLines}A\x1B[J`);
|
|
186
|
+
}
|
|
187
|
+
render(true);
|
|
188
|
+
return new Promise((resolve) => {
|
|
189
|
+
const handler = (chunk) => {
|
|
190
|
+
let i = 0;
|
|
191
|
+
while (i < chunk.length) {
|
|
192
|
+
if (chunk[i] === 0x1b) {
|
|
193
|
+
const rem = chunk.length - i - 1;
|
|
194
|
+
if (rem === 0) {
|
|
195
|
+
clear();
|
|
196
|
+
stdin.removeListener("data", handler);
|
|
197
|
+
setRaw(wasRaw);
|
|
198
|
+
resolve(null);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const next = consumeEscapeSeq(chunk, i);
|
|
202
|
+
if (next !== null) {
|
|
203
|
+
const c = next - i;
|
|
204
|
+
if (c === 3 && chunk[i + 1] === 0x5b) {
|
|
205
|
+
if (chunk[i + 2] === 0x41 && selected > 0) {
|
|
206
|
+
selected--;
|
|
207
|
+
render(false);
|
|
208
|
+
}
|
|
209
|
+
if (chunk[i + 2] === 0x42 && selected < items.length - 1) {
|
|
210
|
+
selected++;
|
|
211
|
+
render(false);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
i = next;
|
|
215
|
+
}
|
|
216
|
+
else
|
|
217
|
+
i++;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const byte = chunk[i];
|
|
221
|
+
i++;
|
|
222
|
+
if (byte === 0x0d || byte === 0x0a) {
|
|
223
|
+
clear();
|
|
224
|
+
stdin.removeListener("data", handler);
|
|
225
|
+
setRaw(wasRaw);
|
|
226
|
+
resolve(items[selected]?.value ?? null);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (byte === 0x03) {
|
|
230
|
+
clear();
|
|
231
|
+
stdin.removeListener("data", handler);
|
|
232
|
+
setRaw(wasRaw);
|
|
233
|
+
resolve(null);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
stdin.on("data", handler);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/* ==================================================================
|
|
242
|
+
* Line reader
|
|
243
|
+
* ================================================================== */
|
|
244
|
+
const history = [];
|
|
245
|
+
let historyIdx = -1;
|
|
246
|
+
/** 当前 readLine 的重绘函数 (窗口 resize 时调用,重绘输入行) */
|
|
247
|
+
let currentRedraw = null;
|
|
248
|
+
async function readLine(prompt, initial = "") {
|
|
249
|
+
const wasRaw = stdin.isRaw ?? false;
|
|
250
|
+
setRaw(true);
|
|
251
|
+
stdin.resume();
|
|
252
|
+
let line = initial;
|
|
253
|
+
let suggestion = "";
|
|
254
|
+
let tabState = null;
|
|
255
|
+
/**
|
|
256
|
+
* 重绘当前行:prompt + line + 灰色 autosuggestion,光标停在 line 末尾。
|
|
257
|
+
* autosuggestion 取当前 word 的第一个补全候选的剩余部分 (仅当 current 非空时)。
|
|
258
|
+
*/
|
|
259
|
+
function redraw() {
|
|
260
|
+
const parsed = parseLine(line);
|
|
261
|
+
const candidates = getCompletions(parsed);
|
|
262
|
+
const first = candidates[0];
|
|
263
|
+
suggestion =
|
|
264
|
+
parsed.current && first && first !== parsed.current && first.startsWith(parsed.current)
|
|
265
|
+
? first.slice(parsed.current.length)
|
|
266
|
+
: "";
|
|
267
|
+
stdout.write("\r\x1B[K" + prompt + line);
|
|
268
|
+
if (suggestion) {
|
|
269
|
+
stdout.write(c.dim(suggestion));
|
|
270
|
+
stdout.write("\x1B[" + suggestion.length + "D");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
redraw();
|
|
274
|
+
currentRedraw = redraw;
|
|
275
|
+
return new Promise((resolve) => {
|
|
276
|
+
const handler = (chunk) => {
|
|
277
|
+
let i = 0;
|
|
278
|
+
while (i < chunk.length) {
|
|
279
|
+
if (chunk[i] === 0x1b) {
|
|
280
|
+
const next = consumeEscapeSeq(chunk, i);
|
|
281
|
+
if (next !== null) {
|
|
282
|
+
const len = next - i;
|
|
283
|
+
if (len === 3 && chunk[i + 1] === 0x5b) {
|
|
284
|
+
const fin = chunk[i + 2];
|
|
285
|
+
if (fin === 0x41 && historyIdx > 0) {
|
|
286
|
+
historyIdx--;
|
|
287
|
+
line = history[historyIdx] ?? "";
|
|
288
|
+
tabState = null;
|
|
289
|
+
redraw();
|
|
290
|
+
}
|
|
291
|
+
else if (fin === 0x42) {
|
|
292
|
+
if (historyIdx < history.length - 1) {
|
|
293
|
+
historyIdx++;
|
|
294
|
+
line = history[historyIdx] ?? "";
|
|
295
|
+
tabState = null;
|
|
296
|
+
redraw();
|
|
297
|
+
}
|
|
298
|
+
else if (historyIdx === history.length - 1) {
|
|
299
|
+
historyIdx = history.length;
|
|
300
|
+
line = "";
|
|
301
|
+
tabState = null;
|
|
302
|
+
redraw();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else if (fin === 0x43 && suggestion) {
|
|
306
|
+
/* → 接受 autosuggestion */
|
|
307
|
+
line += suggestion;
|
|
308
|
+
tabState = null;
|
|
309
|
+
redraw();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
i = next;
|
|
313
|
+
}
|
|
314
|
+
else
|
|
315
|
+
i++;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const byte = chunk[i];
|
|
319
|
+
i++;
|
|
320
|
+
if (byte === 0x0d || byte === 0x0a) {
|
|
321
|
+
stdin.removeListener("data", handler);
|
|
322
|
+
setRaw(wasRaw);
|
|
323
|
+
/* 清掉灰色 suggestion,留下干净的 prompt+line 再换行 */
|
|
324
|
+
stdout.write("\r\x1B[K" + prompt + line + "\r\n");
|
|
325
|
+
if (line.length > 0) {
|
|
326
|
+
history.push(line);
|
|
327
|
+
if (history.length > 100)
|
|
328
|
+
history.shift();
|
|
329
|
+
}
|
|
330
|
+
historyIdx = history.length;
|
|
331
|
+
resolve(line);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (byte === 0x03) {
|
|
335
|
+
stdin.removeListener("data", handler);
|
|
336
|
+
setRaw(wasRaw);
|
|
337
|
+
if (line.length > 0) {
|
|
338
|
+
line = "";
|
|
339
|
+
tabState = null;
|
|
340
|
+
/* Ctrl+C 清空当前输入,不弹 suggestion */
|
|
341
|
+
stdout.write("\r\x1B[K" + prompt);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
stdout.write("\r\n");
|
|
345
|
+
resolve(null);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
/* Tab — context-aware completion, cycle on repeated Tab, no auto-space */
|
|
349
|
+
if (byte === 0x09) {
|
|
350
|
+
if (tabState && line === tabState.completedLine) {
|
|
351
|
+
tabState.idx = (tabState.idx + 1) % tabState.candidates.length;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const parsed = parseLine(line);
|
|
355
|
+
const candidates = getCompletions(parsed);
|
|
356
|
+
if (candidates.length === 0) {
|
|
357
|
+
tabState = null;
|
|
358
|
+
redraw();
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
tabState = {
|
|
362
|
+
candidates,
|
|
363
|
+
idx: 0,
|
|
364
|
+
wordStart: line.length - parsed.current.length,
|
|
365
|
+
completedLine: "",
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
const match = tabState.candidates[tabState.idx];
|
|
369
|
+
line = line.slice(0, tabState.wordStart) + match;
|
|
370
|
+
tabState.completedLine = line;
|
|
371
|
+
redraw();
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
/* Ctrl+L */
|
|
375
|
+
if (byte === 0x0c) {
|
|
376
|
+
stdin.removeListener("data", handler);
|
|
377
|
+
setRaw(wasRaw);
|
|
378
|
+
resolve("__CTRLL__" + line);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (byte === 0x7f || byte === 0x08) {
|
|
382
|
+
if (line.length > 0) {
|
|
383
|
+
line = line.slice(0, -1);
|
|
384
|
+
}
|
|
385
|
+
tabState = null;
|
|
386
|
+
redraw();
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (byte >= 0x20 && byte <= 0x7e) {
|
|
390
|
+
line += String.fromCharCode(byte);
|
|
391
|
+
tabState = null;
|
|
392
|
+
redraw();
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
stdin.on("data", handler);
|
|
398
|
+
}).finally(() => {
|
|
399
|
+
currentRedraw = null;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
const LEVEL_ITEMS = [
|
|
403
|
+
{ label: c.blue("INFO"), value: "info" },
|
|
404
|
+
{ label: c.yellow("WARN"), value: "warn" },
|
|
405
|
+
{ label: c.red("ERROR"), value: "error" },
|
|
406
|
+
{ label: c.dim("DEBUG"), value: "debug" },
|
|
407
|
+
{ label: c.green("SUCCESS"), value: "success" },
|
|
408
|
+
];
|
|
409
|
+
export const SOURCE_ITEMS = [
|
|
410
|
+
{ label: c.green("BDServer"), value: "bds" },
|
|
411
|
+
{ label: c.blue("DataBase"), value: "db" },
|
|
412
|
+
{ label: c.purple("QQBridge"), value: "qq" },
|
|
413
|
+
{ label: c.yellow(" LL-BOT "), value: "llbot" },
|
|
414
|
+
{ label: c.cyan(" SYSTEM "), value: "system" },
|
|
415
|
+
{ label: c.orange(" UPDATE "), value: "update" },
|
|
416
|
+
{ label: c.red("BDSTools"), value: "bds-tools" },
|
|
417
|
+
];
|
|
418
|
+
function pushAndRender(log, filter) {
|
|
419
|
+
if (filter.levels.length && !filter.levels.includes(log.level))
|
|
420
|
+
return;
|
|
421
|
+
if (filter.sources.length && !filter.sources.includes(log.source))
|
|
422
|
+
return;
|
|
423
|
+
stdout.write(`\r\x1B[K${wrapLogLine(formatLog(log), 26)}\n`);
|
|
424
|
+
}
|
|
425
|
+
/* ==================================================================
|
|
426
|
+
* START REPL
|
|
427
|
+
* ================================================================== */
|
|
428
|
+
export async function startRepl() {
|
|
429
|
+
let stopping = false;
|
|
430
|
+
const shutdown = async () => {
|
|
431
|
+
if (stopping)
|
|
432
|
+
return;
|
|
433
|
+
stopping = true;
|
|
434
|
+
stdout.write(c.dim("stopping services...\n"));
|
|
435
|
+
await stopAll();
|
|
436
|
+
stdout.write(c.dim("bye\n"));
|
|
437
|
+
};
|
|
438
|
+
const onSigint = () => {
|
|
439
|
+
stdout.write(c.yellow("\nforce stopping services...\n"));
|
|
440
|
+
forceStopAll();
|
|
441
|
+
process.exit(130);
|
|
442
|
+
};
|
|
443
|
+
process.on("SIGINT", onSigint);
|
|
444
|
+
if (!stdin.isTTY) {
|
|
445
|
+
console.log(c.dim(" Non-interactive mode (pipe detected)\n"));
|
|
446
|
+
for await (const line of (await import("node:readline/promises")).createInterface({
|
|
447
|
+
input: stdin,
|
|
448
|
+
output: stdout,
|
|
449
|
+
terminal: false,
|
|
450
|
+
})) {
|
|
451
|
+
const t = line.trim();
|
|
452
|
+
if (!t)
|
|
453
|
+
continue;
|
|
454
|
+
const p = t.split(/\s+/);
|
|
455
|
+
if (["quit", "exit", "q"].includes(p[0]))
|
|
456
|
+
break;
|
|
457
|
+
if (p[0] === "init") {
|
|
458
|
+
(await import("./wizard.js")).runWizard();
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
await execCmd(p);
|
|
462
|
+
}
|
|
463
|
+
await shutdown();
|
|
464
|
+
process.off("SIGINT", onSigint);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
console.clear();
|
|
468
|
+
stdout.write(welcome);
|
|
469
|
+
let filter = { levels: [], sources: [] };
|
|
470
|
+
const unsub = onLog((log) => pushAndRender(log, filter));
|
|
471
|
+
/** 窗口大小变化时重绘可见日志 + 输入行 (按新宽度换行) */
|
|
472
|
+
function onResize() {
|
|
473
|
+
if (!currentRedraw)
|
|
474
|
+
return;
|
|
475
|
+
const rows = process.stdout.rows || 24;
|
|
476
|
+
stdout.write("\x1B[H\x1B[2J");
|
|
477
|
+
const all = getAllLogs();
|
|
478
|
+
const out = [];
|
|
479
|
+
let usedRows = 0;
|
|
480
|
+
for (let i = all.length - 1; i >= 0; i--) {
|
|
481
|
+
const log = all[i];
|
|
482
|
+
if (filter.levels.length && !filter.levels.includes(log.level))
|
|
483
|
+
continue;
|
|
484
|
+
if (filter.sources.length && !filter.sources.includes(log.source))
|
|
485
|
+
continue;
|
|
486
|
+
const wrapped = wrapLogLine(formatLog(log), 26);
|
|
487
|
+
const logRows = wrapped.split("\n").length;
|
|
488
|
+
if (usedRows + logRows > rows - 2)
|
|
489
|
+
break;
|
|
490
|
+
out.unshift(wrapped);
|
|
491
|
+
usedRows += logRows;
|
|
492
|
+
}
|
|
493
|
+
for (const l of out)
|
|
494
|
+
stdout.write(l + "\n");
|
|
495
|
+
currentRedraw();
|
|
496
|
+
}
|
|
497
|
+
process.stdout.on("resize", onResize);
|
|
498
|
+
let pendingInput = "";
|
|
499
|
+
while (true) {
|
|
500
|
+
const raw = await readLine(c.text(" ❯ "), pendingInput);
|
|
501
|
+
pendingInput = "";
|
|
502
|
+
if (raw === null)
|
|
503
|
+
break;
|
|
504
|
+
/* Ctrl+L — filter */
|
|
505
|
+
if (raw.startsWith("__CTRLL__")) {
|
|
506
|
+
stdout.write(c.dim(`\nLEVEL──────────SOURCE\n`));
|
|
507
|
+
const lvl = await simpleSelect([{ label: "ALL", value: "" }, ...LEVEL_ITEMS]);
|
|
508
|
+
if (lvl === null)
|
|
509
|
+
continue;
|
|
510
|
+
const src = await simpleSelect([{ label: "ALL", value: "" }, ...SOURCE_ITEMS]);
|
|
511
|
+
if (src === null)
|
|
512
|
+
continue;
|
|
513
|
+
filter = { levels: lvl ? [lvl] : [], sources: src ? [src] : [] };
|
|
514
|
+
stdout.write(c.dim(`filter: ${lvl || "*"} / ${src || "*"}\n`));
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const trimmed = raw.trim();
|
|
518
|
+
if (!trimmed)
|
|
519
|
+
continue;
|
|
520
|
+
try {
|
|
521
|
+
await execCmd(trimmed.split(/\s+/));
|
|
522
|
+
}
|
|
523
|
+
catch (e) {
|
|
524
|
+
if (e === "QUIT")
|
|
525
|
+
break;
|
|
526
|
+
console.log(c.red(`Error: ${e.message}`));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
process.stdout.off("resize", onResize);
|
|
530
|
+
unsub();
|
|
531
|
+
await shutdown();
|
|
532
|
+
process.off("SIGINT", onSigint);
|
|
533
|
+
}
|
|
534
|
+
async function execCmd(parts) {
|
|
535
|
+
const [cmd, ...args] = parts;
|
|
536
|
+
switch (cmd) {
|
|
537
|
+
case "help":
|
|
538
|
+
case "h":
|
|
539
|
+
case "?":
|
|
540
|
+
stdout.write(HELP);
|
|
541
|
+
break;
|
|
542
|
+
case "version":
|
|
543
|
+
stdout.write(`${version}\n`);
|
|
544
|
+
break;
|
|
545
|
+
case "status":
|
|
546
|
+
stdout.write(cmdStatus() + "\n");
|
|
547
|
+
break;
|
|
548
|
+
case "logs":
|
|
549
|
+
case "log": {
|
|
550
|
+
const out = cmdLogs(args, () => {
|
|
551
|
+
if (!stdin.isTTY) {
|
|
552
|
+
stdout.write(c.yellow("follow mode requires TTY\n"));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
if (out)
|
|
557
|
+
stdout.write(out + "\n");
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
case "start":
|
|
561
|
+
if (args[0] === "-all" || args[0] === "all" || args[0] === "--all")
|
|
562
|
+
stdout.write((await cmdStartAll()) + "\n");
|
|
563
|
+
else if (args[0])
|
|
564
|
+
stdout.write((await cmdStart(args[0])) + "\n");
|
|
565
|
+
else
|
|
566
|
+
stdout.write(c.yellow("Usage: start <service>|-all\n"));
|
|
567
|
+
break;
|
|
568
|
+
case "stop":
|
|
569
|
+
if (args[0] === "-all" || args[0] === "all" || args[0] === "--all")
|
|
570
|
+
stdout.write((await cmdStopAll()) + "\n");
|
|
571
|
+
else if (args[0])
|
|
572
|
+
stdout.write((await cmdStop(args[0])) + "\n");
|
|
573
|
+
else
|
|
574
|
+
stdout.write(c.yellow("Usage: stop <service>|-all\n"));
|
|
575
|
+
break;
|
|
576
|
+
case "restart":
|
|
577
|
+
if (args[0] === "-all" || args[0] === "all" || args[0] === "--all") {
|
|
578
|
+
await cmdStopAll();
|
|
579
|
+
stdout.write((await cmdStartAll()) + "\n");
|
|
580
|
+
}
|
|
581
|
+
else if (args[0])
|
|
582
|
+
stdout.write((await cmdRestart(args[0])) + "\n");
|
|
583
|
+
else
|
|
584
|
+
stdout.write(c.yellow("Usage: restart <service>|-all\n"));
|
|
585
|
+
break;
|
|
586
|
+
case "send": {
|
|
587
|
+
const svc = args[0] ?? "";
|
|
588
|
+
const msg = args.slice(1).join(" ");
|
|
589
|
+
stdout.write((await cmdSend(svc, msg)) + "\n");
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
case "init": {
|
|
593
|
+
const { runWizard } = await import("./wizard.js");
|
|
594
|
+
await runWizard();
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
case "update":
|
|
598
|
+
await cmdUpdate(args);
|
|
599
|
+
break;
|
|
600
|
+
case "remote": {
|
|
601
|
+
const [subcommand, controllerUrl, enrollmentToken, name] = args;
|
|
602
|
+
if (subcommand === "status") {
|
|
603
|
+
stdout.write(JSON.stringify(remoteStatus(), null, 2) + "\n");
|
|
604
|
+
}
|
|
605
|
+
else if (subcommand === "enroll" && controllerUrl && enrollmentToken) {
|
|
606
|
+
const agentName = name ?? process.env.COMPUTERNAME ?? "sfmc-agent";
|
|
607
|
+
stdout.write(`Enrolled remote agent: ${await enrollRemoteAgent(controllerUrl, enrollmentToken, agentName)}\n`);
|
|
608
|
+
startRemoteAgent();
|
|
609
|
+
}
|
|
610
|
+
else if (subcommand === "disable") {
|
|
611
|
+
disableRemoteAgent();
|
|
612
|
+
stdout.write(c.dim("Remote agent disabled\n"));
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
stdout.write("Usage: remote enroll <controller-url> <enrollment-token> [name] | remote status | remote disable\n");
|
|
616
|
+
}
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
case "module":
|
|
620
|
+
case "mod": {
|
|
621
|
+
const [sub, ...subRest] = args;
|
|
622
|
+
switch (sub) {
|
|
623
|
+
case "list":
|
|
624
|
+
stdout.write((await cmdModuleList(subRest)) + "\n");
|
|
625
|
+
break;
|
|
626
|
+
case "install":
|
|
627
|
+
stdout.write((await cmdModuleInstall(subRest)) + "\n");
|
|
628
|
+
break;
|
|
629
|
+
case "uninstall":
|
|
630
|
+
case "remove":
|
|
631
|
+
stdout.write((await cmdModuleUninstall(subRest)) + "\n");
|
|
632
|
+
break;
|
|
633
|
+
case "verify":
|
|
634
|
+
stdout.write((await cmdModuleVerify(subRest)) + "\n");
|
|
635
|
+
break;
|
|
636
|
+
case "info":
|
|
637
|
+
stdout.write((await cmdModuleInfo(subRest)) + "\n");
|
|
638
|
+
break;
|
|
639
|
+
default:
|
|
640
|
+
stdout.write(c.yellow("Usage: module <list|install|uninstall|verify|info> [args]\n"));
|
|
641
|
+
}
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
case "quit":
|
|
645
|
+
case "exit":
|
|
646
|
+
case "q":
|
|
647
|
+
throw "QUIT";
|
|
648
|
+
default:
|
|
649
|
+
stdout.write(c.yellow(`Unknown: ${cmd} (try: help)\n`));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
//# sourceMappingURL=repl.js.map
|