@sfmc-bds/cli 0.1.0 → 0.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +7 -1
  2. package/dist/cli-result.d.ts +12 -0
  3. package/dist/cli-result.d.ts.map +1 -0
  4. package/dist/cli-result.js +15 -0
  5. package/dist/cli-result.js.map +7 -0
  6. package/dist/commands-behavior-pack.d.ts +11 -42
  7. package/dist/commands-behavior-pack.d.ts.map +1 -1
  8. package/dist/commands-behavior-pack.js +40 -159
  9. package/dist/commands-behavior-pack.js.map +7 -1
  10. package/dist/commands-reload.d.ts +9 -0
  11. package/dist/commands-reload.d.ts.map +1 -0
  12. package/dist/commands-reload.js +30 -0
  13. package/dist/commands-reload.js.map +7 -0
  14. package/dist/commands.d.ts +7 -0
  15. package/dist/commands.d.ts.map +1 -1
  16. package/dist/commands.js +182 -172
  17. package/dist/commands.js.map +7 -1
  18. package/dist/i18n/index.d.ts +26 -0
  19. package/dist/i18n/index.d.ts.map +1 -0
  20. package/dist/i18n/index.js +107 -0
  21. package/dist/i18n/index.js.map +7 -0
  22. package/dist/i18n/locales/en.d.ts +381 -0
  23. package/dist/i18n/locales/en.d.ts.map +1 -0
  24. package/dist/i18n/locales/en.js +393 -0
  25. package/dist/i18n/locales/en.js.map +7 -0
  26. package/dist/i18n/locales/zh-CN.d.ts +4 -0
  27. package/dist/i18n/locales/zh-CN.d.ts.map +1 -0
  28. package/dist/i18n/locales/zh-CN.js +379 -0
  29. package/dist/i18n/locales/zh-CN.js.map +7 -0
  30. package/dist/i18n/types.d.ts +7 -0
  31. package/dist/i18n/types.d.ts.map +1 -0
  32. package/dist/i18n/types.js +17 -0
  33. package/dist/i18n/types.js.map +7 -0
  34. package/dist/index.d.ts +5 -11
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +51 -43
  37. package/dist/index.js.map +7 -1
  38. package/dist/interactive-prompts.d.ts +8 -0
  39. package/dist/interactive-prompts.d.ts.map +1 -0
  40. package/dist/interactive-prompts.js +57 -0
  41. package/dist/interactive-prompts.js.map +7 -0
  42. package/dist/locale-command.d.ts +3 -0
  43. package/dist/locale-command.d.ts.map +1 -0
  44. package/dist/locale-command.js +19 -0
  45. package/dist/locale-command.js.map +7 -0
  46. package/dist/logs.d.ts +22 -4
  47. package/dist/logs.d.ts.map +1 -1
  48. package/dist/logs.js +208 -193
  49. package/dist/logs.js.map +7 -1
  50. package/dist/main.js +135 -173
  51. package/dist/main.js.map +7 -1
  52. package/dist/module-commands.d.ts +43 -4
  53. package/dist/module-commands.d.ts.map +1 -1
  54. package/dist/module-commands.js +440 -327
  55. package/dist/module-commands.js.map +7 -1
  56. package/dist/module-fingerprint.d.ts +7 -0
  57. package/dist/module-fingerprint.d.ts.map +1 -0
  58. package/dist/module-fingerprint.js +31 -0
  59. package/dist/module-fingerprint.js.map +7 -0
  60. package/dist/module-wizard.d.ts +14 -0
  61. package/dist/module-wizard.d.ts.map +1 -0
  62. package/dist/module-wizard.js +313 -0
  63. package/dist/module-wizard.js.map +7 -0
  64. package/dist/pack-lifecycle.d.ts +95 -0
  65. package/dist/pack-lifecycle.d.ts.map +1 -0
  66. package/dist/pack-lifecycle.js +634 -0
  67. package/dist/pack-lifecycle.js.map +7 -0
  68. package/dist/pack-update/bindings.d.ts +12 -0
  69. package/dist/pack-update/bindings.d.ts.map +1 -0
  70. package/dist/pack-update/bindings.js +64 -0
  71. package/dist/pack-update/bindings.js.map +7 -0
  72. package/dist/pack-update/config.d.ts +22 -0
  73. package/dist/pack-update/config.d.ts.map +1 -0
  74. package/dist/pack-update/config.js +141 -0
  75. package/dist/pack-update/config.js.map +7 -0
  76. package/dist/pack-update/providers/curseforge.d.ts +23 -0
  77. package/dist/pack-update/providers/curseforge.d.ts.map +1 -0
  78. package/dist/pack-update/providers/curseforge.js +209 -0
  79. package/dist/pack-update/providers/curseforge.js.map +7 -0
  80. package/dist/pack-update/providers/index.d.ts +16 -0
  81. package/dist/pack-update/providers/index.d.ts.map +1 -0
  82. package/dist/pack-update/providers/index.js +37 -0
  83. package/dist/pack-update/providers/index.js.map +7 -0
  84. package/dist/pack-update/service.d.ts +21 -0
  85. package/dist/pack-update/service.d.ts.map +1 -0
  86. package/dist/pack-update/service.js +567 -0
  87. package/dist/pack-update/service.js.map +7 -0
  88. package/dist/pack-update/types.d.ts +126 -0
  89. package/dist/pack-update/types.d.ts.map +1 -0
  90. package/dist/pack-update/types.js +8 -0
  91. package/dist/pack-update/types.js.map +7 -0
  92. package/dist/pack-update/version-policy.d.ts +80 -0
  93. package/dist/pack-update/version-policy.d.ts.map +1 -0
  94. package/dist/pack-update/version-policy.js +216 -0
  95. package/dist/pack-update/version-policy.js.map +7 -0
  96. package/dist/registry.d.ts +12 -4
  97. package/dist/registry.d.ts.map +1 -1
  98. package/dist/registry.js +73 -106
  99. package/dist/registry.js.map +7 -1
  100. package/dist/remote-agent.d.ts.map +1 -1
  101. package/dist/remote-agent.js +160 -176
  102. package/dist/remote-agent.js.map +7 -1
  103. package/dist/repl.d.ts +4 -0
  104. package/dist/repl.d.ts.map +1 -1
  105. package/dist/repl.js +683 -605
  106. package/dist/repl.js.map +7 -1
  107. package/dist/runtime.d.ts +40 -14
  108. package/dist/runtime.d.ts.map +1 -1
  109. package/dist/runtime.js +185 -62
  110. package/dist/runtime.js.map +7 -1
  111. package/dist/services.d.ts +2 -0
  112. package/dist/services.d.ts.map +1 -1
  113. package/dist/services.js +251 -238
  114. package/dist/services.js.map +7 -1
  115. package/dist/sfmc-modules-root.d.ts +24 -0
  116. package/dist/sfmc-modules-root.d.ts.map +1 -0
  117. package/dist/sfmc-modules-root.js +70 -0
  118. package/dist/sfmc-modules-root.js.map +7 -0
  119. package/dist/theme.d.ts +4 -0
  120. package/dist/theme.d.ts.map +1 -1
  121. package/dist/theme.js +134 -48
  122. package/dist/theme.js.map +7 -1
  123. package/dist/wizard.d.ts.map +1 -1
  124. package/dist/wizard.js +363 -438
  125. package/dist/wizard.js.map +7 -1
  126. package/dist/world-packs.d.ts +22 -0
  127. package/dist/world-packs.d.ts.map +1 -0
  128. package/dist/world-packs.js +872 -0
  129. package/dist/world-packs.js.map +7 -0
  130. package/package.json +10 -10
  131. package/dist/dispatcher.d.ts +0 -13
  132. package/dist/dispatcher.d.ts.map +0 -1
  133. package/dist/dispatcher.js +0 -23
  134. package/dist/dispatcher.js.map +0 -1
  135. package/dist/terminal.d.ts +0 -3
  136. package/dist/terminal.d.ts.map +0 -1
  137. package/dist/terminal.js +0 -178
  138. package/dist/terminal.js.map +0 -1
package/dist/repl.js CHANGED
@@ -1,652 +1,730 @@
1
1
  import process, { stdin, stdout } from "node:process";
2
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";
3
+ import {
4
+ cmdLogs,
5
+ cmdRestart,
6
+ cmdSend,
7
+ cmdStart,
8
+ cmdStartAll,
9
+ cmdStatus,
10
+ cmdStop,
11
+ cmdStopAll,
12
+ cmdUpdate
13
+ } from "./commands.js";
14
+ import {
15
+ formatLog,
16
+ getAllLogs,
17
+ getRecentLogs,
18
+ logPrefixWidth,
19
+ onLog,
20
+ SOURCE_META,
21
+ wrapLogLine
22
+ } from "./logs.js";
23
+ import { pauseAllProgress, resumeAllProgress } from "@sfmc-bds/sdk/logs";
24
+ import {
25
+ dispatchModuleCommand,
26
+ isModuleCommand,
27
+ listInstalledModuleIdsSync,
28
+ MODULE_CMD_NAMES,
29
+ MODULE_SUBCOMMANDS
30
+ } from "./module-commands.js";
31
+ import { listRegistryModuleIdsSync } from "./registry.js";
6
32
  import { disableRemoteAgent, enrollRemoteAgent, remoteStatus, startRemoteAgent } from "./remote-agent.js";
7
33
  import { forceStopAll, SERVICE_NAMES, stopAll } from "./services.js";
34
+ import { cmdLocale } from "./locale-command.js";
35
+ import { t } from "./i18n/index.js";
36
+ import { listSfmcModulePackages, resolveSfmcModulesRoot } from "./sfmc-modules-root.js";
8
37
  import { c } from "./theme.js";
38
+ import { dispatchPacksCommand, isPacksCommand, PACKS_SUBCOMMANDS } from "./world-packs.js";
9
39
  function setRaw(v) {
10
- try {
11
- if (stdin.isTTY && typeof stdin.setRawMode === "function")
12
- stdin.setRawMode(v);
13
- }
14
- catch { }
40
+ try {
41
+ if (stdin.isTTY && typeof stdin.setRawMode === "function") stdin.setRawMode(v);
42
+ } catch {
43
+ }
15
44
  }
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
45
+ const welcome = `
46
+
47
+ ${c.text(`\u282A\u2841\u286F\u2801`)}
48
+ ${c.text(`\u2812\u2801\u2803`)}${c.purple(`\u2804`)}
49
+ ${c.text(`\u2877\u2847\u284E\u2801`)} ${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}
50
+ ${c.text(`\u2803\u2803\u2811\u2802`)} ${c.dim(`help \xB7 Ctrl+L \xB7 \u21B9 \xB7 \u2192 \xB7 \u2191\u2193`)}
51
+
21
52
  `;
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
53
+ const version = `
54
+
55
+ ${c.text(`\u282A\u2841\u286F\u2801`)}
56
+ ${c.text(`\u2812\u2801\u2803`)}${c.purple(`\u2804`)}
57
+ ${c.text(`\u2877\u2847\u284E\u2801`)} ${c.dim(`https://github.com/DogeLakeDev/ScriptsForMinecraftServer`)}
58
+ ${c.text(`\u2803\u2803\u2811\u2802`)} ${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}
59
+
27
60
  `;
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
61
+ function getHelp() {
62
+ return `
63
+ ${c.bold("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E")}
64
+ ${c.bold("\u2502")} ${c.green(t("help.title"))}${" ".repeat(Math.max(1, 52 - t("help.title").length))}${c.bold("\u2502")}
65
+ ${c.bold("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F")}
66
+
67
+ ${c.bold(t("help.section.service"))}
68
+ ${c.green("status")} ${t("help.status")}
69
+ ${c.green("logs")} <svc> [-n N] [-f] ${t("help.logs")}
70
+ ${c.green("start")} <svc>|-all ${t("help.start")}
71
+ ${c.green("stop")} <svc>|-all ${t("help.stop")}
72
+ ${c.green("restart")}<svc>|-all ${t("help.restart")}
73
+ ${c.green("send")} <svc> <msg> ${t("help.send")}
74
+
75
+ ${c.bold(t("help.section.update"))}
37
76
  ${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
77
+ ${t("help.update")}
78
+
79
+ ${c.bold(t("help.section.remote"))} ${c.dim("[beta]")}
80
+ ${c.green("remote")} status ${t("help.remote.status")}
81
+ ${c.green("remote")} enroll <url> <token> [name]
82
+ ${t("help.remote.enroll")}
83
+ ${c.green("remote")} disable ${t("help.remote.disable")}
84
+
85
+ ${c.bold(t("help.section.module"))}
86
+ ${c.green("module")}/${c.green("mod")} list
87
+ ${t("help.module.list")}
88
+ ${c.green("module")} search [id] ${t("help.module.search")}
89
+ ${c.green("module")} install <id> [--from <source>] [--link]
90
+ ${t("help.module.install")}
91
+ ${c.green("module")} uninstall <id> ${t("help.module.uninstall")}
92
+ ${c.green("module")} verify [id] ${t("help.module.verify")}
93
+ ${c.green("module")} info <id> ${t("help.module.info")}
94
+ ${c.green("module")} enable|disable <id>
95
+ ${t("help.module.toggle")}
96
+ ${c.green("module")} create ${t("help.module.create")}
97
+ ${c.green("module")} link [id] ${t("help.module.link")}
98
+ ${c.green("module")} dev ${t("help.module.dev")}
99
+ ${c.green("module")} build ${t("help.module.build")}
100
+ ${c.green("module")} reload [--build-only]
101
+ ${t("help.module.reload")}
102
+
103
+ ${c.bold(t("help.section.addon"))}
104
+ ${c.green("addon")}/${c.green("packs")} list|search|enable|disable|bump|install|scan|doctor|path
105
+ ${t("help.addon")}
53
106
 
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
107
+ ${c.bold(t("help.section.general"))}
108
+ ${c.green("init")} ${t("help.init")}
109
+ ${c.green("locale")} [zh|en] ${t("help.locale")}
110
+ ${c.green("version")} ${t("help.version")}
111
+ ${c.green("help")} ${t("help.help")}
112
+ ${c.green("quit")} / ${c.green("exit")} ${t("help.quit")}
113
+
114
+ ${c.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
115
+ ${c.dim(t("help.shortcuts"))}
116
+ ${c.dim("Tab")} ${t("help.shortcut.tab")}
117
+ ${c.dim("\u2192")} ${t("help.shortcut.right")}
118
+ ${c.dim("Ctrl+L")} ${t("help.shortcut.ctrll")}
119
+ ${c.dim("\u2191\u2193")} ${t("help.shortcut.history")}
59
120
  `;
121
+ }
122
+ const HELP = {
123
+ toString() {
124
+ return getHelp();
125
+ },
126
+ valueOf() {
127
+ return getHelp();
128
+ }
129
+ };
60
130
  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",
131
+ "status",
132
+ "logs",
133
+ "start",
134
+ "stop",
135
+ "restart",
136
+ "send",
137
+ "init",
138
+ "locale",
139
+ "lang",
140
+ "update",
141
+ "remote",
142
+ ...MODULE_CMD_NAMES,
143
+ "packs",
144
+ "addon",
145
+ "version",
146
+ "help",
147
+ "quit",
148
+ "exit"
75
149
  ];
76
- /**
77
- * 解析当前输入行,提取命令名、参数位置、正在输入的 word。
78
- * 末尾空格视为"刚结束一个 word,准备输入下一个"。
79
- */
80
150
  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] };
151
+ const endsWithSpace = line.length > 0 && /\s$/.test(line);
152
+ const trimmed = line.trim();
153
+ if (!trimmed) return { cmd: "", words: [], argIndex: 0, current: "" };
154
+ const tokens = trimmed.split(/\s+/);
155
+ if (endsWithSpace) {
156
+ return {
157
+ cmd: tokens[0] ?? "",
158
+ words: tokens.slice(1),
159
+ argIndex: tokens.length - 1,
160
+ current: ""
161
+ };
162
+ }
163
+ if (tokens.length === 1) return { cmd: "", words: [], argIndex: 0, current: tokens[0] };
164
+ return {
165
+ cmd: tokens[0],
166
+ words: tokens.slice(1, -1),
167
+ argIndex: tokens.length - 2,
168
+ current: tokens[tokens.length - 1]
169
+ };
92
170
  }
93
- /**
94
- * 根据命令 + 参数位置返回补全候选 (区分命令,不再把服务名当成所有指令的二级参数)。
95
- */
96
171
  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 [];
172
+ const { cmd, words, argIndex, current } = parsed;
173
+ const sw = (s) => s.startsWith(current);
174
+ if (!cmd) return COMMANDS.filter(sw);
175
+ switch (cmd) {
176
+ case "logs":
177
+ case "log":
178
+ if (argIndex === 0) return SERVICE_NAMES.filter(sw);
179
+ if (argIndex === 1) return ["-n", "-f"].filter(sw);
180
+ return [];
181
+ case "start":
182
+ case "stop":
183
+ case "restart":
184
+ if (argIndex === 0) return ["-all", ...SERVICE_NAMES].filter(sw);
185
+ return [];
186
+ case "send":
187
+ if (argIndex === 0) return SERVICE_NAMES.filter(sw);
188
+ return [];
189
+ case "update":
190
+ return ["--check-only", "--force", "--channel=release", "--channel=preview"].filter(sw);
191
+ case "remote":
192
+ if (argIndex === 0) return ["status", "enroll", "disable"].filter(sw);
193
+ return [];
194
+ case "packs":
195
+ case "addon":
196
+ if (argIndex === 0) return [...PACKS_SUBCOMMANDS].filter(sw);
197
+ if (argIndex === 1 && ["list"].includes(words[0] ?? "")) {
198
+ return ["--kind", "--search"].filter(sw);
199
+ }
200
+ return [];
201
+ default: {
202
+ if (!isModuleCommand(cmd)) return [];
203
+ if (argIndex === 0) return [...MODULE_SUBCOMMANDS].filter(sw);
204
+ const verb = words[0] ?? "";
205
+ if (argIndex === 1 && verb === "search") {
206
+ return listRegistryModuleIdsSync().filter(sw);
207
+ }
208
+ if (argIndex === 1 && ["info", "uninstall", "remove", "verify", "enable", "disable"].includes(verb)) {
209
+ return listInstalledModuleIdsSync().filter(sw);
210
+ }
211
+ if (argIndex === 1 && verb === "reload") {
212
+ return ["--build-only"].filter(sw);
213
+ }
214
+ if (argIndex === 1 && verb === "link") {
215
+ const root = resolveSfmcModulesRoot();
216
+ if (root) {
217
+ const ids = listSfmcModulePackages(root).map((p) => p.id).filter(sw);
218
+ if (ids.length) return ids;
219
+ }
220
+ return ["--from"].filter(sw);
221
+ }
222
+ if (argIndex >= 1 && verb === "install") {
223
+ return ["--from", "--sha256", "--link"].filter(sw);
224
+ }
225
+ if (argIndex >= 2 && verb === "link") {
226
+ return ["--from"].filter(sw);
227
+ }
228
+ if (argIndex >= 1 && verb === "list") {
229
+ return ["--from", "--sha256"].filter(sw);
230
+ }
231
+ return [];
137
232
  }
233
+ }
138
234
  }
139
- /* ==================================================================
140
- * Escape sequence consumer
141
- * ================================================================== */
142
235
  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;
236
+ if (chunk[i] !== 27) return null;
237
+ const rem = chunk.length - i - 1;
238
+ if (rem >= 2 && chunk[i + 1] === 91) {
239
+ let j = i + 2;
240
+ while (j < chunk.length && chunk[j] >= 48 && chunk[j] <= 63) j++;
241
+ while (j < chunk.length && chunk[j] >= 32 && chunk[j] <= 47) j++;
242
+ if (j < chunk.length && chunk[j] >= 64 && chunk[j] <= 126) j++;
243
+ return j;
244
+ }
245
+ if (rem >= 2 && chunk[i + 1] === 79) return i + 3;
246
+ if (rem >= 1) return i + 2;
247
+ return i + 1;
161
248
  }
162
249
  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);
250
+ const wasRaw = stdin.isRaw ?? false;
251
+ setRaw(true);
252
+ stdin.resume();
253
+ let selected = 0;
254
+ const h = Math.min(items.length, 8);
255
+ let lastLines = h;
256
+ function render(first) {
257
+ if (!first) {
258
+ stdout.write(`\x1B[${lastLines}A\x1B[J`);
259
+ } else {
260
+ stdout.write("\x1B[J");
183
261
  }
184
- function clear() {
185
- stdout.write(`\x1B[${lastLines}A\x1B[J`);
262
+ lastLines = h;
263
+ let out = "";
264
+ for (let i = 0; i < h; i++) {
265
+ const cur = i === selected ? `\u25C9 ${c.text(items[i].label)}` : `\u25CB ${c.text(items[i].label)}`;
266
+ out += `${cur}
267
+ `;
186
268
  }
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
- }
269
+ stdout.write(out);
270
+ }
271
+ function clear() {
272
+ stdout.write(`\x1B[${lastLines}A\x1B[J`);
273
+ }
274
+ render(true);
275
+ return new Promise((resolve) => {
276
+ const handler = (chunk) => {
277
+ let i = 0;
278
+ while (i < chunk.length) {
279
+ if (chunk[i] === 27) {
280
+ const rem = chunk.length - i - 1;
281
+ if (rem === 0) {
282
+ clear();
283
+ stdin.removeListener("data", handler);
284
+ setRaw(wasRaw);
285
+ resolve(null);
286
+ return;
287
+ }
288
+ const next = consumeEscapeSeq(chunk, i);
289
+ if (next !== null) {
290
+ const c2 = next - i;
291
+ if (c2 === 3 && chunk[i + 1] === 91) {
292
+ if (chunk[i + 2] === 65 && selected > 0) {
293
+ selected--;
294
+ render(false);
295
+ }
296
+ if (chunk[i + 2] === 66 && selected < items.length - 1) {
297
+ selected++;
298
+ render(false);
299
+ }
236
300
  }
237
- };
238
- stdin.on("data", handler);
239
- });
301
+ i = next;
302
+ } else i++;
303
+ continue;
304
+ }
305
+ const byte = chunk[i];
306
+ i++;
307
+ if (byte === 13 || byte === 10) {
308
+ clear();
309
+ stdin.removeListener("data", handler);
310
+ setRaw(wasRaw);
311
+ resolve(items[selected]?.value ?? null);
312
+ return;
313
+ }
314
+ if (byte === 3) {
315
+ clear();
316
+ stdin.removeListener("data", handler);
317
+ setRaw(wasRaw);
318
+ resolve(null);
319
+ return;
320
+ }
321
+ }
322
+ };
323
+ stdin.on("data", handler);
324
+ });
240
325
  }
241
- /* ==================================================================
242
- * Line reader
243
- * ================================================================== */
244
326
  const history = [];
245
327
  let historyIdx = -1;
246
- /** 当前 readLine 的重绘函数 (窗口 resize 时调用,重绘输入行) */
247
328
  let currentRedraw = null;
248
329
  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
- }
330
+ const wasRaw = stdin.isRaw ?? false;
331
+ setRaw(true);
332
+ stdin.resume();
333
+ let line = initial;
334
+ let suggestion = "";
335
+ let tabState = null;
336
+ function redraw() {
337
+ const parsed = parseLine(line);
338
+ const candidates = getCompletions(parsed);
339
+ const first = candidates[0];
340
+ suggestion = parsed.current && first && first !== parsed.current && first.startsWith(parsed.current) ? first.slice(parsed.current.length) : "";
341
+ stdout.write("\r\x1B[K" + prompt + line);
342
+ if (suggestion) {
343
+ stdout.write(c.dim(suggestion));
344
+ stdout.write("\x1B[" + suggestion.length + "D");
272
345
  }
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;
346
+ }
347
+ redraw();
348
+ currentRedraw = redraw;
349
+ return new Promise((resolve) => {
350
+ const handler = (chunk) => {
351
+ let i = 0;
352
+ while (i < chunk.length) {
353
+ if (chunk[i] === 27) {
354
+ const next = consumeEscapeSeq(chunk, i);
355
+ if (next !== null) {
356
+ const len = next - i;
357
+ if (len === 3 && chunk[i + 1] === 91) {
358
+ const fin = chunk[i + 2];
359
+ if (fin === 65 && historyIdx > 0) {
360
+ historyIdx--;
361
+ line = history[historyIdx] ?? "";
362
+ tabState = null;
363
+ redraw();
364
+ } else if (fin === 66) {
365
+ if (historyIdx < history.length - 1) {
366
+ historyIdx++;
367
+ line = history[historyIdx] ?? "";
368
+ tabState = null;
369
+ redraw();
370
+ } else if (historyIdx === history.length - 1) {
371
+ historyIdx = history.length;
372
+ line = "";
373
+ tabState = null;
374
+ redraw();
394
375
  }
376
+ } else if (fin === 67 && suggestion) {
377
+ line += suggestion;
378
+ tabState = null;
379
+ redraw();
380
+ }
395
381
  }
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);
382
+ i = next;
383
+ } else i++;
384
+ continue;
462
385
  }
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;
386
+ const byte = chunk[i];
387
+ i++;
388
+ if (byte === 13 || byte === 10) {
389
+ stdin.removeListener("data", handler);
390
+ setRaw(wasRaw);
391
+ stdout.write("\r\x1B[K" + prompt + line + "\r\n");
392
+ if (line.length > 0) {
393
+ history.push(line);
394
+ if (history.length > 100) history.shift();
395
+ }
396
+ historyIdx = history.length;
397
+ resolve(line);
398
+ return;
492
399
  }
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`));
400
+ if (byte === 3) {
401
+ stdin.removeListener("data", handler);
402
+ setRaw(wasRaw);
403
+ if (line.length > 0) {
404
+ line = "";
405
+ tabState = null;
406
+ stdout.write("\r\x1B[K" + prompt);
515
407
  continue;
408
+ }
409
+ stdout.write("\r\n");
410
+ resolve(null);
411
+ return;
516
412
  }
517
- const trimmed = raw.trim();
518
- if (!trimmed)
519
- continue;
520
- try {
521
- await execCmd(trimmed.split(/\s+/));
413
+ if (byte === 9) {
414
+ if (tabState && line === tabState.completedLine) {
415
+ tabState.idx = (tabState.idx + 1) % tabState.candidates.length;
416
+ } else {
417
+ const parsed = parseLine(line);
418
+ const candidates = getCompletions(parsed);
419
+ if (candidates.length === 0) {
420
+ tabState = null;
421
+ redraw();
422
+ continue;
423
+ }
424
+ tabState = {
425
+ candidates,
426
+ idx: 0,
427
+ wordStart: line.length - parsed.current.length,
428
+ completedLine: ""
429
+ };
430
+ }
431
+ const match = tabState.candidates[tabState.idx];
432
+ line = line.slice(0, tabState.wordStart) + match;
433
+ tabState.completedLine = line;
434
+ redraw();
435
+ continue;
436
+ }
437
+ if (byte === 12) {
438
+ stdin.removeListener("data", handler);
439
+ setRaw(wasRaw);
440
+ resolve("__CTRLL__" + line);
441
+ return;
442
+ }
443
+ if (byte === 127 || byte === 8) {
444
+ if (line.length > 0) {
445
+ line = line.slice(0, -1);
446
+ }
447
+ tabState = null;
448
+ redraw();
449
+ continue;
522
450
  }
523
- catch (e) {
524
- if (e === "QUIT")
525
- break;
526
- console.log(c.red(`Error: ${e.message}`));
451
+ if (byte >= 32 && byte <= 126) {
452
+ line += String.fromCharCode(byte);
453
+ tabState = null;
454
+ redraw();
455
+ continue;
527
456
  }
457
+ }
458
+ };
459
+ stdin.on("data", handler);
460
+ }).finally(() => {
461
+ currentRedraw = null;
462
+ });
463
+ }
464
+ const LEVEL_ITEMS = [
465
+ { label: c.blue("INFO"), value: "info" },
466
+ { label: c.yellow("WARN"), value: "warn" },
467
+ { label: c.red("ERROR"), value: "error" },
468
+ { label: c.dim("DEBUG"), value: "debug" },
469
+ { label: c.green("SUCCESS"), value: "success" }
470
+ ];
471
+ const SOURCE_ITEMS = SOURCE_META.map((m) => ({
472
+ label: m.paint(m.name),
473
+ value: m.value
474
+ }));
475
+ function historyItems() {
476
+ return [
477
+ { label: t("repl.history.live"), value: "none" },
478
+ { label: t("repl.history.last50"), value: "count:50" },
479
+ { label: t("repl.history.last100"), value: "count:100" },
480
+ { label: t("repl.history.last500"), value: "count:500" },
481
+ { label: t("repl.history.last1000"), value: "count:1000" },
482
+ { label: t("repl.history.last1min"), value: "time:60000" },
483
+ { label: t("repl.history.last5min"), value: "time:300000" },
484
+ { label: t("repl.history.last15min"), value: "time:900000" },
485
+ { label: t("repl.history.last1hour"), value: "time:3600000" },
486
+ { label: t("repl.history.all"), value: "all" }
487
+ ];
488
+ }
489
+ function queryHistory(filter, window) {
490
+ if (!window || window === "none") return [];
491
+ const match = (l) => {
492
+ if (filter.levels.length && !filter.levels.includes(l.level)) return false;
493
+ if (filter.sources.length && !filter.sources.includes(l.source)) return false;
494
+ return true;
495
+ };
496
+ if (window === "all") return getAllLogs().filter(match);
497
+ if (window.startsWith("count:")) {
498
+ const n = Number(window.slice("count:".length));
499
+ if (!Number.isFinite(n) || n <= 0) return [];
500
+ return getRecentLogs(n, filter.levels, filter.sources);
501
+ }
502
+ if (window.startsWith("time:")) {
503
+ const ms = Number(window.slice("time:".length));
504
+ if (!Number.isFinite(ms) || ms <= 0) return [];
505
+ const since = Date.now() - ms;
506
+ return getAllLogs().filter((l) => match(l) && l.time.getTime() >= since);
507
+ }
508
+ return [];
509
+ }
510
+ function pushAndRender(log, filter) {
511
+ if (filter.levels.length && !filter.levels.includes(log.level)) return;
512
+ if (filter.sources.length && !filter.sources.includes(log.source)) return;
513
+ pauseAllProgress();
514
+ try {
515
+ stdout.write(`\r\x1B[K${wrapLogLine(formatLog(log), logPrefixWidth(log))}
516
+ `);
517
+ } finally {
518
+ resumeAllProgress();
519
+ }
520
+ currentRedraw?.();
521
+ }
522
+ async function startRepl() {
523
+ let stopping = false;
524
+ const shutdown = async () => {
525
+ if (stopping) return;
526
+ stopping = true;
527
+ stdout.write(c.dim(t("repl.stopping") + "\n"));
528
+ await stopAll();
529
+ stdout.write(c.dim(t("repl.bye") + "\n"));
530
+ };
531
+ const onSigint = () => {
532
+ stdout.write(c.yellow("\n" + t("repl.forceStop") + "\n"));
533
+ forceStopAll();
534
+ process.exit(130);
535
+ };
536
+ process.on("SIGINT", onSigint);
537
+ if (!stdin.isTTY) {
538
+ console.log(c.dim(t("repl.nonInteractive")));
539
+ for await (const line of (await import("node:readline/promises")).createInterface({
540
+ input: stdin,
541
+ output: stdout,
542
+ terminal: false
543
+ })) {
544
+ const t2 = line.trim();
545
+ if (!t2) continue;
546
+ const p = t2.split(/\s+/);
547
+ if (["quit", "exit", "q"].includes(p[0])) break;
548
+ if (p[0] === "init") {
549
+ (await import("./wizard.js")).runWizard();
550
+ continue;
551
+ }
552
+ await execCmd(p);
528
553
  }
529
- process.stdout.off("resize", onResize);
530
- unsub();
531
554
  await shutdown();
532
555
  process.off("SIGINT", onSigint);
556
+ return;
557
+ }
558
+ console.clear();
559
+ stdout.write(welcome);
560
+ let filter = { levels: [], sources: [] };
561
+ const unsub = onLog((log) => pushAndRender(log, filter));
562
+ function onResize() {
563
+ if (!currentRedraw) return;
564
+ const rows = process.stdout.rows || 24;
565
+ stdout.write("\x1B[H\x1B[2J");
566
+ const all = getAllLogs();
567
+ const out = [];
568
+ let usedRows = 0;
569
+ for (let i = all.length - 1; i >= 0; i--) {
570
+ const log = all[i];
571
+ if (filter.levels.length && !filter.levels.includes(log.level)) continue;
572
+ if (filter.sources.length && !filter.sources.includes(log.source)) continue;
573
+ const wrapped = wrapLogLine(formatLog(log), logPrefixWidth(log));
574
+ const logRows = wrapped.split("\n").length;
575
+ if (usedRows + logRows > rows - 2) break;
576
+ out.unshift(wrapped);
577
+ usedRows += logRows;
578
+ }
579
+ for (const l of out) stdout.write(l + "\n");
580
+ currentRedraw();
581
+ }
582
+ process.stdout.on("resize", onResize);
583
+ let pendingInput = "";
584
+ while (true) {
585
+ const raw = await readLine(c.text(" \u276F "), pendingInput);
586
+ pendingInput = "";
587
+ if (raw === null) break;
588
+ if (raw.startsWith("__CTRLL__")) {
589
+ stdout.write(c.dim(`
590
+ LEVEL\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500SOURCE\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500HISTORY
591
+ `));
592
+ const histChoices = historyItems();
593
+ const lvl = await simpleSelect([{ label: t("common.all"), value: "" }, ...LEVEL_ITEMS]);
594
+ if (lvl === null) continue;
595
+ const src = await simpleSelect([{ label: t("common.all"), value: "" }, ...SOURCE_ITEMS]);
596
+ if (src === null) continue;
597
+ const hist = await simpleSelect(histChoices);
598
+ if (hist === null) continue;
599
+ filter = { levels: lvl ? [lvl] : [], sources: src ? [src] : [] };
600
+ const replay = queryHistory(filter, hist);
601
+ if (replay.length > 0) {
602
+ stdout.write(c.dim(t("repl.historyHeader", { count: replay.length }) + "\n"));
603
+ for (const log of replay) {
604
+ stdout.write(`${wrapLogLine(formatLog(log), logPrefixWidth(log))}
605
+ `);
606
+ }
607
+ }
608
+ const histLabel = histChoices.find((i) => i.value === hist)?.label ?? hist;
609
+ stdout.write(
610
+ c.dim(t("repl.filter", { level: lvl || "*", source: src || "*", history: histLabel }) + "\n")
611
+ );
612
+ continue;
613
+ }
614
+ const trimmed = raw.trim();
615
+ if (!trimmed) continue;
616
+ try {
617
+ await execCmd(trimmed.split(/\s+/));
618
+ } catch (e) {
619
+ if (e === "QUIT") break;
620
+ console.log(c.red(t("common.error", { message: e.message })));
621
+ }
622
+ }
623
+ process.stdout.off("resize", onResize);
624
+ unsub();
625
+ await shutdown();
626
+ process.off("SIGINT", onSigint);
533
627
  }
534
628
  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;
629
+ const [cmd, ...args] = parts;
630
+ switch (cmd) {
631
+ case "help":
632
+ case "h":
633
+ case "?":
634
+ stdout.write(getHelp());
635
+ break;
636
+ case "version":
637
+ stdout.write(`${version}
638
+ `);
639
+ break;
640
+ case "locale":
641
+ case "lang":
642
+ stdout.write(cmdLocale(args) + "\n");
643
+ break;
644
+ case "status":
645
+ stdout.write(cmdStatus() + "\n");
646
+ break;
647
+ case "logs":
648
+ case "log": {
649
+ const out = cmdLogs(args, () => {
650
+ if (!stdin.isTTY) {
651
+ stdout.write(c.yellow(t("repl.followRequiresTty") + "\n"));
652
+ return;
596
653
  }
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`));
654
+ });
655
+ if (out) stdout.write(out + "\n");
656
+ break;
657
+ }
658
+ case "start":
659
+ if (args[0] === "-all" || args[0] === "all" || args[0] === "--all") stdout.write(await cmdStartAll() + "\n");
660
+ else if (args[0]) stdout.write(await cmdStart(args[0]) + "\n");
661
+ else stdout.write(c.yellow(t("svc.start.usageShort") + "\n"));
662
+ break;
663
+ case "stop":
664
+ if (args[0] === "-all" || args[0] === "all" || args[0] === "--all") stdout.write(await cmdStopAll() + "\n");
665
+ else if (args[0]) stdout.write(await cmdStop(args[0]) + "\n");
666
+ else stdout.write(c.yellow(t("svc.stop.usageShort") + "\n"));
667
+ break;
668
+ case "restart":
669
+ if (args[0] === "-all" || args[0] === "all" || args[0] === "--all") {
670
+ await cmdStopAll();
671
+ stdout.write(await cmdStartAll() + "\n");
672
+ } else if (args[0]) stdout.write(await cmdRestart(args[0]) + "\n");
673
+ else stdout.write(c.yellow(t("svc.restart.usageShort") + "\n"));
674
+ break;
675
+ case "send": {
676
+ const svc = args[0] ?? "";
677
+ const msg = args.slice(1).join(" ");
678
+ stdout.write(await cmdSend(svc, msg) + "\n");
679
+ break;
680
+ }
681
+ case "init": {
682
+ const { runWizard } = await import("./wizard.js");
683
+ await runWizard();
684
+ break;
685
+ }
686
+ case "update":
687
+ await cmdUpdate(args);
688
+ break;
689
+ case "remote": {
690
+ const [subcommand, controllerUrl, enrollmentToken, name] = args;
691
+ if (subcommand === "status") {
692
+ stdout.write(JSON.stringify(remoteStatus(), null, 2) + "\n");
693
+ } else if (subcommand === "enroll" && controllerUrl && enrollmentToken) {
694
+ const agentName = name ?? process.env.COMPUTERNAME ?? "sfmc-agent";
695
+ const id = await enrollRemoteAgent(controllerUrl, enrollmentToken, agentName);
696
+ stdout.write(t("remote.enrolled", { id }) + "\n");
697
+ startRemoteAgent();
698
+ } else if (subcommand === "disable") {
699
+ disableRemoteAgent();
700
+ stdout.write(c.dim(t("remote.disabled") + "\n"));
701
+ } else {
702
+ stdout.write(t("remote.usageShort") + "\n");
703
+ }
704
+ break;
650
705
  }
706
+ case "quit":
707
+ case "exit":
708
+ case "q":
709
+ throw "QUIT";
710
+ default:
711
+ if (isModuleCommand(cmd)) {
712
+ const [sub, ...subRest] = args;
713
+ stdout.write(await dispatchModuleCommand(sub, subRest) + "\n");
714
+ break;
715
+ }
716
+ if (isPacksCommand(cmd)) {
717
+ const [sub, ...subRest] = args;
718
+ stdout.write(await dispatchPacksCommand(sub, subRest) + "\n");
719
+ break;
720
+ }
721
+ stdout.write(c.yellow(t("common.unknownShort", { cmd: cmd ?? "" }) + "\n"));
722
+ }
651
723
  }
652
- //# sourceMappingURL=repl.js.map
724
+ export {
725
+ HELP,
726
+ SOURCE_ITEMS,
727
+ getHelp,
728
+ startRepl
729
+ };
730
+ //# sourceMappingURL=repl.js.map