@sfmc-bds/cli 0.2.0-beta.5 → 0.2.0-beta.7

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 (163) hide show
  1. package/dist/argv-parse.d.ts +13 -0
  2. package/dist/argv-parse.d.ts.map +1 -0
  3. package/dist/argv-parse.js +32 -0
  4. package/dist/argv-parse.js.map +7 -0
  5. package/dist/cli-gate.d.ts +11 -0
  6. package/dist/cli-gate.d.ts.map +1 -0
  7. package/dist/cli-gate.js +48 -0
  8. package/dist/cli-gate.js.map +7 -0
  9. package/dist/cmd-remote.d.ts +13 -0
  10. package/dist/cmd-remote.d.ts.map +1 -0
  11. package/dist/cmd-remote.js +38 -0
  12. package/dist/cmd-remote.js.map +7 -0
  13. package/dist/command-palette.d.ts +61 -0
  14. package/dist/command-palette.d.ts.map +1 -0
  15. package/dist/command-palette.js +352 -0
  16. package/dist/command-palette.js.map +7 -0
  17. package/dist/command-surface.d.ts +95 -0
  18. package/dist/command-surface.d.ts.map +1 -0
  19. package/dist/command-surface.js +324 -0
  20. package/dist/command-surface.js.map +7 -0
  21. package/dist/commands-reload.js +2 -2
  22. package/dist/commands-reload.js.map +2 -2
  23. package/dist/commands.d.ts +1 -1
  24. package/dist/commands.d.ts.map +1 -1
  25. package/dist/commands.js +79 -17
  26. package/dist/commands.js.map +2 -2
  27. package/dist/debug-command.d.ts +5 -0
  28. package/dist/debug-command.d.ts.map +1 -0
  29. package/dist/debug-command.js +118 -0
  30. package/dist/debug-command.js.map +7 -0
  31. package/dist/help-text.d.ts +3 -0
  32. package/dist/help-text.d.ts.map +1 -0
  33. package/dist/help-text.js +164 -0
  34. package/dist/help-text.js.map +7 -0
  35. package/dist/i18n/locales/en.d.ts +93 -68
  36. package/dist/i18n/locales/en.d.ts.map +1 -1
  37. package/dist/i18n/locales/en.js +97 -69
  38. package/dist/i18n/locales/en.js.map +2 -2
  39. package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
  40. package/dist/i18n/locales/zh-CN.js +140 -106
  41. package/dist/i18n/locales/zh-CN.js.map +2 -2
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +6 -0
  45. package/dist/index.js.map +2 -2
  46. package/dist/logs.d.ts +16 -1
  47. package/dist/logs.d.ts.map +1 -1
  48. package/dist/logs.js +80 -11
  49. package/dist/logs.js.map +2 -2
  50. package/dist/main.js +58 -44
  51. package/dist/main.js.map +2 -2
  52. package/dist/module-commands.d.ts +25 -35
  53. package/dist/module-commands.d.ts.map +1 -1
  54. package/dist/module-commands.js +107 -65
  55. package/dist/module-commands.js.map +2 -2
  56. package/dist/module-pack-build.d.ts +10 -0
  57. package/dist/module-pack-build.d.ts.map +1 -0
  58. package/dist/module-pack-build.js +430 -0
  59. package/dist/module-pack-build.js.map +7 -0
  60. package/dist/module-publish.d.ts +69 -0
  61. package/dist/module-publish.d.ts.map +1 -0
  62. package/dist/module-publish.js +478 -0
  63. package/dist/module-publish.js.map +7 -0
  64. package/dist/module-watch.d.ts +32 -0
  65. package/dist/module-watch.d.ts.map +1 -0
  66. package/dist/module-watch.js +171 -0
  67. package/dist/module-watch.js.map +7 -0
  68. package/dist/pack-lifecycle.d.ts +1 -1
  69. package/dist/pack-lifecycle.d.ts.map +1 -1
  70. package/dist/pack-lifecycle.js +3 -3
  71. package/dist/pack-lifecycle.js.map +2 -2
  72. package/dist/pack-update/index.d.ts +18 -0
  73. package/dist/pack-update/index.d.ts.map +1 -0
  74. package/dist/pack-update/index.js +113 -0
  75. package/dist/pack-update/index.js.map +7 -0
  76. package/dist/registry.d.ts +6 -3
  77. package/dist/registry.d.ts.map +1 -1
  78. package/dist/registry.js +15 -3
  79. package/dist/registry.js.map +2 -2
  80. package/dist/remote-agent.d.ts.map +1 -1
  81. package/dist/remote-agent.js +7 -5
  82. package/dist/remote-agent.js.map +2 -2
  83. package/dist/repl-windows/format-display.d.ts +10 -0
  84. package/dist/repl-windows/format-display.d.ts.map +1 -0
  85. package/dist/repl-windows/format-display.js +57 -0
  86. package/dist/repl-windows/format-display.js.map +7 -0
  87. package/dist/repl-windows/host.d.ts +60 -0
  88. package/dist/repl-windows/host.d.ts.map +1 -0
  89. package/dist/repl-windows/host.js +179 -0
  90. package/dist/repl-windows/host.js.map +7 -0
  91. package/dist/repl-windows/index.d.ts +9 -0
  92. package/dist/repl-windows/index.d.ts.map +1 -0
  93. package/dist/repl-windows/index.js +13 -0
  94. package/dist/repl-windows/index.js.map +7 -0
  95. package/dist/repl-windows/logs-filter-window.d.ts +15 -0
  96. package/dist/repl-windows/logs-filter-window.d.ts.map +1 -0
  97. package/dist/repl-windows/logs-filter-window.js +77 -0
  98. package/dist/repl-windows/logs-filter-window.js.map +7 -0
  99. package/dist/repl-windows/service-window.d.ts +4 -0
  100. package/dist/repl-windows/service-window.d.ts.map +1 -0
  101. package/dist/repl-windows/service-window.js +28 -0
  102. package/dist/repl-windows/service-window.js.map +7 -0
  103. package/dist/repl-windows/types.d.ts +56 -0
  104. package/dist/repl-windows/types.d.ts.map +1 -0
  105. package/dist/repl-windows/types.js +1 -0
  106. package/dist/repl-windows/types.js.map +7 -0
  107. package/dist/repl.d.ts +7 -8
  108. package/dist/repl.d.ts.map +1 -1
  109. package/dist/repl.js +707 -417
  110. package/dist/repl.js.map +3 -3
  111. package/dist/runtime.d.ts +2 -0
  112. package/dist/runtime.d.ts.map +1 -1
  113. package/dist/runtime.js +4 -0
  114. package/dist/runtime.js.map +2 -2
  115. package/dist/send-target.d.ts +11 -0
  116. package/dist/send-target.d.ts.map +1 -0
  117. package/dist/send-target.js +38 -0
  118. package/dist/send-target.js.map +7 -0
  119. package/dist/services.d.ts +21 -1
  120. package/dist/services.d.ts.map +1 -1
  121. package/dist/services.js +146 -7
  122. package/dist/services.js.map +2 -2
  123. package/dist/theme.js +1 -1
  124. package/dist/theme.js.map +2 -2
  125. package/dist/wizard.d.ts.map +1 -1
  126. package/dist/wizard.js +11 -9
  127. package/dist/wizard.js.map +2 -2
  128. package/dist/world-packs.d.ts.map +1 -1
  129. package/dist/world-packs.js +6 -3
  130. package/dist/world-packs.js.map +2 -2
  131. package/package.json +5 -5
  132. package/dist/module-wizard.d.ts +0 -14
  133. package/dist/module-wizard.d.ts.map +0 -1
  134. package/dist/module-wizard.js +0 -313
  135. package/dist/module-wizard.js.map +0 -7
  136. package/dist/pack-update/bindings.d.ts +0 -12
  137. package/dist/pack-update/bindings.d.ts.map +0 -1
  138. package/dist/pack-update/bindings.js +0 -64
  139. package/dist/pack-update/bindings.js.map +0 -7
  140. package/dist/pack-update/config.d.ts +0 -22
  141. package/dist/pack-update/config.d.ts.map +0 -1
  142. package/dist/pack-update/config.js +0 -141
  143. package/dist/pack-update/config.js.map +0 -7
  144. package/dist/pack-update/providers/curseforge.d.ts +0 -23
  145. package/dist/pack-update/providers/curseforge.d.ts.map +0 -1
  146. package/dist/pack-update/providers/curseforge.js +0 -229
  147. package/dist/pack-update/providers/curseforge.js.map +0 -7
  148. package/dist/pack-update/providers/index.d.ts +0 -16
  149. package/dist/pack-update/providers/index.d.ts.map +0 -1
  150. package/dist/pack-update/providers/index.js +0 -37
  151. package/dist/pack-update/providers/index.js.map +0 -7
  152. package/dist/pack-update/service.d.ts +0 -21
  153. package/dist/pack-update/service.d.ts.map +0 -1
  154. package/dist/pack-update/service.js +0 -572
  155. package/dist/pack-update/service.js.map +0 -7
  156. package/dist/pack-update/types.d.ts +0 -126
  157. package/dist/pack-update/types.d.ts.map +0 -1
  158. package/dist/pack-update/types.js +0 -8
  159. package/dist/pack-update/types.js.map +0 -7
  160. package/dist/pack-update/version-policy.d.ts +0 -81
  161. package/dist/pack-update/version-policy.d.ts.map +0 -1
  162. package/dist/pack-update/version-policy.js +0 -210
  163. package/dist/pack-update/version-policy.js.map +0 -7
package/dist/repl.js CHANGED
@@ -1,152 +1,248 @@
1
+ import { pauseAllProgress, resumeAllProgress } from "@sfmc-bds/sdk/logs";
2
+ import chalk from "chalk";
1
3
  import process, { stdin, stdout } from "node:process";
2
4
  import pkg from "../package.json" with { type: "json" };
5
+ import { gateModuleSub, gatePacksSub, gateTopLevel } from "./cli-gate.js";
6
+ import {
7
+ activeNode,
8
+ clampPaletteSelection,
9
+ commitSelection,
10
+ formatPaletteCascadeLines,
11
+ paletteGhost,
12
+ promptSlashColumn,
13
+ resolvePaletteView
14
+ } from "./command-palette.js";
3
15
  import {
4
- cmdLogs,
5
- cmdRestart,
6
- cmdSend,
7
- cmdStart,
8
- cmdStartAll,
9
- cmdStatus,
10
- cmdStop,
11
- cmdStopAll,
12
- cmdUpdate
13
- } from "./commands.js";
16
+ listVisiblePacksSubs,
17
+ listVisibleTopLevelNames,
18
+ resolveModuleTopShorthand
19
+ } from "./command-surface.js";
20
+ import { cmdRestart, cmdSend, cmdStart, cmdStartAll, cmdStatus, cmdStop, cmdStopAll, cmdUpdate } from "./commands.js";
21
+ import { cmdRemote } from "./cmd-remote.js";
22
+ import { cmdDebug } from "./debug-command.js";
23
+ import { getHelp as buildHelp } from "./help-text.js";
24
+ import { t } from "./i18n/index.js";
25
+ import { cmdLocale } from "./locale-command.js";
14
26
  import {
15
27
  formatLog,
16
28
  getAllLogs,
17
- getRecentLogs,
18
29
  logPrefixWidth,
19
30
  onLog,
31
+ readDiskLogs,
20
32
  SOURCE_META,
21
33
  wrapLogLine
22
34
  } from "./logs.js";
23
- import { pauseAllProgress, resumeAllProgress } from "@sfmc-bds/sdk/logs";
24
35
  import {
25
36
  dispatchModuleCommand,
37
+ getVisibleModuleSubcommands,
26
38
  isModuleCommand,
27
- listInstalledModuleIdsSync,
28
- MODULE_CMD_NAMES,
29
- MODULE_SUBCOMMANDS
39
+ listInstalledModuleIdsSync
30
40
  } from "./module-commands.js";
31
41
  import { listRegistryModuleIdsSync } from "./registry.js";
32
- import { disableRemoteAgent, enrollRemoteAgent, remoteStatus, startRemoteAgent } from "./remote-agent.js";
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";
37
- import { c } from "./theme.js";
38
- import { dispatchPacksCommand, isPacksCommand, PACKS_SUBCOMMANDS } from "./world-packs.js";
42
+ import { stopRemoteAgent } from "./remote-agent.js";
43
+ import {
44
+ createLogsFilterWindow,
45
+ createServiceWindow,
46
+ serviceWindowId,
47
+ WindowHost
48
+ } from "./repl-windows/index.js";
49
+ import { listActiveSendTargets, paintSendPrompt, plainPrompt } from "./send-target.js";
50
+ import { forceStopAll, onServiceStateChange, SERVICE_NAMES, stopAll } from "./services.js";
51
+ import { c, T } from "./theme.js";
52
+ import { dispatchPacksCommand, isPacksCommand } from "./world-packs.js";
53
+ const REPL_MODE = "repl";
54
+ function getDisplayWidth(str) {
55
+ const plain = str.replace(/\x1b\[[0-9;]*m/g, "");
56
+ let width = 0;
57
+ for (const ch of plain) {
58
+ if (ch.codePointAt(0) > 127) {
59
+ width += 2;
60
+ } else {
61
+ width += 1;
62
+ }
63
+ }
64
+ return width;
65
+ }
66
+ function charDisplayWidth(ch) {
67
+ return ch.codePointAt(0) > 127 ? 2 : 1;
68
+ }
39
69
  function setRaw(v) {
40
70
  try {
41
71
  if (stdin.isTTY && typeof stdin.setRawMode === "function") stdin.setRawMode(v);
42
72
  } catch {
43
73
  }
44
74
  }
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
-
52
- `;
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
-
60
- `;
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"))}
76
- ${c.green("update")} [--check-only] [--channel=release|preview]
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")}
106
-
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")}
75
+ const LOGO_ROWS = ["\u256D\u2500\u256E\u256D\u2500\u2574\u256D\u252C\u256E\u256D\u2500\u2574", "\u2570\u2500\u256E\u251C\u2574 \u2502\u2502\u2502\u2502 ", "\u2570\u2500\u256F\u2575 \u2575 \u2575\u2570\u2500\u2500\u2500"];
76
+ const LOGO_PAD = " ";
77
+ const LOGO_TOP_GAP = " ";
78
+ const LOGO_MID_GAP = " ";
79
+ const SHORTCUT_HINT = ` / \xB7 Tab \xB7 Ctrl+P \xB7 Ctrl+L \xB7 \u2190\u2192 \xB7 \u2191\u2193`;
80
+ const LOGO_STROKE_RAW = [
81
+ // S
82
+ [0, 1],
83
+ [0, 2],
84
+ [0, 0],
85
+ [1, 0],
86
+ [1, 1],
87
+ [1, 2],
88
+ [2, 2],
89
+ [2, 1],
90
+ [2, 0],
91
+ // F
92
+ [0, 3],
93
+ [1, 3],
94
+ [2, 3],
95
+ [0, 4],
96
+ [0, 5],
97
+ [1, 4],
98
+ // M
99
+ [0, 6],
100
+ [1, 6],
101
+ [2, 6],
102
+ [0, 7],
103
+ [1, 7],
104
+ [0, 8],
105
+ [1, 8],
106
+ [2, 8],
107
+ // C(末笔落到 ╰───,随后无缝接横线延长)
108
+ [0, 10],
109
+ [0, 11],
110
+ [0, 9],
111
+ [1, 9],
112
+ [2, 9],
113
+ [2, 10],
114
+ [2, 11],
115
+ [2, 12]
116
+ ];
117
+ const LOGO_STROKES = LOGO_STROKE_RAW.filter(([r, col]) => {
118
+ const ch = LOGO_ROWS[r]?.[col];
119
+ return ch !== void 0 && ch !== " ";
120
+ });
121
+ const metaPlain = `Scripts For Minecraft Server v${pkg.version}`;
122
+ const META_ACCENT_INDEX = /* @__PURE__ */ new Set([0, 8, 12, 16]);
123
+ const metaUnlit = chalk.hex(T.subtle);
124
+ const meta = `${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}`;
125
+ const metaWidth = getDisplayWidth(metaPlain);
126
+ const lineLength = metaWidth + 5;
127
+ function buildTextAtProgress(plain, litColumns, paintLit) {
128
+ let cols = 0;
129
+ let out = "";
130
+ for (let i = 0; i < plain.length; i++) {
131
+ const ch = plain[i];
132
+ const w = charDisplayWidth(ch);
133
+ if (cols + w <= litColumns) {
134
+ out += paintLit(ch, i);
135
+ } else {
136
+ out += metaUnlit(ch);
137
+ }
138
+ cols += w;
139
+ }
140
+ return out;
141
+ }
142
+ function buildMetaAtProgress(litColumns) {
143
+ return buildTextAtProgress(metaPlain, litColumns, (ch, i) => META_ACCENT_INDEX.has(i) ? c.text(ch) : c.dim(ch));
144
+ }
145
+ function buildShortcutsAtProgress(litColumns) {
146
+ return buildTextAtProgress(SHORTCUT_HINT, litColumns, (ch) => c.dim(ch));
147
+ }
148
+ function buildLogoRows(strokeCount) {
149
+ const lit = /* @__PURE__ */ new Set();
150
+ const n = Math.min(Math.max(0, strokeCount), LOGO_STROKES.length);
151
+ for (let i = 0; i < n; i++) {
152
+ const [r, col] = LOGO_STROKES[i];
153
+ lit.add(`${r},${col}`);
154
+ }
155
+ return LOGO_ROWS.map((row, r) => {
156
+ let out = "";
157
+ for (let col = 0; col < row.length; col++) {
158
+ const ch = row[col];
159
+ if (ch === " ") {
160
+ out += " ";
161
+ } else if (lit.has(`${r},${col}`)) {
162
+ out += c.text(ch);
163
+ } else {
164
+ out += " ";
165
+ }
166
+ }
167
+ return out;
168
+ });
169
+ }
170
+ function buildWelcomeLines(strokeCount, lineProgress, metaContent, shortcutContent) {
171
+ const [topLogo, midLogo, botLogo] = buildLogoRows(strokeCount);
172
+ const line = "\u2500".repeat(Math.max(0, lineProgress));
173
+ return [
174
+ `
175
+ `,
176
+ `${LOGO_PAD}${topLogo}${LOGO_TOP_GAP}${shortcutContent}`,
177
+ `${LOGO_PAD}${midLogo}${LOGO_MID_GAP}${metaContent}`,
178
+ `${LOGO_PAD}${botLogo}${line}`
179
+ ];
180
+ }
181
+ function paintWelcomeLines(lines, firstFrame) {
182
+ if (!firstFrame) {
183
+ process.stdout.write(`\x1B[${lines.length}A`);
184
+ }
185
+ for (let i = 0; i < lines.length; i++) {
186
+ process.stdout.write(`\x1B[2K\r${lines[i]}`);
187
+ if (i < lines.length - 1) process.stdout.write("\n");
188
+ }
189
+ }
190
+ async function playWelcomeAnimation() {
191
+ const strokeTotal = LOGO_STROKES.length;
192
+ const totalSteps = strokeTotal + lineLength;
193
+ const finalShortcuts = c.dim(SHORTCUT_HINT);
194
+ if (!process.stdout.isTTY) {
195
+ const lines = buildWelcomeLines(strokeTotal, lineLength, c.bold(meta), finalShortcuts);
196
+ process.stdout.write(`
197
+ ${lines.join("\n")}
113
198
 
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")}
120
- `;
199
+ `);
200
+ return;
201
+ }
202
+ let step = 0;
203
+ let firstFrame = true;
204
+ process.stdout.write("\x1B[?25l");
205
+ process.stdout.write("\n");
206
+ return new Promise((resolve) => {
207
+ const done = () => {
208
+ const finalLines = buildWelcomeLines(strokeTotal, lineLength, c.bold(meta), finalShortcuts);
209
+ paintWelcomeLines(finalLines, firstFrame);
210
+ process.stdout.write("\x1B[?25h\n\n");
211
+ resolve();
212
+ };
213
+ const tick = () => {
214
+ const inLogo = step < strokeTotal;
215
+ const strokeCount = Math.min(step + 1, strokeTotal);
216
+ const lineProgress = inLogo ? 0 : Math.min(step - strokeTotal + 1, lineLength);
217
+ const metaContent = buildMetaAtProgress(Math.min(lineProgress, metaWidth));
218
+ const shortcutContent = buildShortcutsAtProgress(lineProgress);
219
+ const lines = buildWelcomeLines(strokeCount, lineProgress, metaContent, shortcutContent);
220
+ paintWelcomeLines(lines, firstFrame);
221
+ firstFrame = false;
222
+ if (step >= totalSteps - 1) {
223
+ done();
224
+ return;
225
+ }
226
+ step += 1;
227
+ setTimeout(tick, step < strokeTotal ? 22 : 5);
228
+ };
229
+ setTimeout(tick, 22);
230
+ });
231
+ }
232
+ function getHelp(mode = "argv") {
233
+ return buildHelp(mode);
121
234
  }
122
235
  const HELP = {
123
236
  toString() {
124
- return getHelp();
237
+ return getHelp("argv");
125
238
  },
126
239
  valueOf() {
127
- return getHelp();
240
+ return getHelp("argv");
128
241
  }
129
242
  };
130
- const COMMANDS = [
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"
149
- ];
243
+ function getCommands() {
244
+ return listVisibleTopLevelNames(REPL_MODE);
245
+ }
150
246
  function parseLine(line) {
151
247
  const endsWithSpace = line.length > 0 && /\s$/.test(line);
152
248
  const trimmed = line.trim();
@@ -169,14 +265,16 @@ function parseLine(line) {
169
265
  };
170
266
  }
171
267
  function getCompletions(parsed) {
172
- const { cmd, words, argIndex, current } = parsed;
268
+ const { words, argIndex, current } = parsed;
269
+ const cmd = parsed.cmd.startsWith("/") ? parsed.cmd.slice(1) : parsed.cmd;
173
270
  const sw = (s) => s.startsWith(current);
174
- if (!cmd) return COMMANDS.filter(sw);
271
+ if (!cmd) {
272
+ const cmds = getCommands().map((n) => n.startsWith("/") ? n : n);
273
+ return ["/", ...cmds].filter(sw);
274
+ }
175
275
  switch (cmd) {
176
276
  case "logs":
177
277
  case "log":
178
- if (argIndex === 0) return SERVICE_NAMES.filter(sw);
179
- if (argIndex === 1) return ["-n", "-f"].filter(sw);
180
278
  return [];
181
279
  case "start":
182
280
  case "stop":
@@ -193,14 +291,14 @@ function getCompletions(parsed) {
193
291
  return [];
194
292
  case "packs":
195
293
  case "addon":
196
- if (argIndex === 0) return [...PACKS_SUBCOMMANDS].filter(sw);
294
+ if (argIndex === 0) return [...listVisiblePacksSubs(REPL_MODE)].filter(sw);
197
295
  if (argIndex === 1 && ["list"].includes(words[0] ?? "")) {
198
296
  return ["--kind", "--search"].filter(sw);
199
297
  }
200
298
  return [];
201
299
  default: {
202
300
  if (!isModuleCommand(cmd)) return [];
203
- if (argIndex === 0) return [...MODULE_SUBCOMMANDS].filter(sw);
301
+ if (argIndex === 0) return [...getVisibleModuleSubcommands(REPL_MODE)].filter(sw);
204
302
  const verb = words[0] ?? "";
205
303
  if (argIndex === 1 && verb === "search") {
206
304
  return listRegistryModuleIdsSync().filter(sw);
@@ -211,20 +309,9 @@ function getCompletions(parsed) {
211
309
  if (argIndex === 1 && verb === "reload") {
212
310
  return ["--build-only"].filter(sw);
213
311
  }
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
312
  if (argIndex >= 1 && verb === "install") {
223
313
  return ["--from", "--sha256", "--link"].filter(sw);
224
314
  }
225
- if (argIndex >= 2 && verb === "link") {
226
- return ["--from"].filter(sw);
227
- }
228
315
  if (argIndex >= 1 && verb === "list") {
229
316
  return ["--from", "--sha256"].filter(sw);
230
317
  }
@@ -246,107 +333,124 @@ function consumeEscapeSeq(chunk, i) {
246
333
  if (rem >= 1) return i + 2;
247
334
  return i + 1;
248
335
  }
249
- async function simpleSelect(items) {
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");
261
- }
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
- `;
268
- }
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
- }
300
- }
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
- });
325
- }
326
336
  const history = [];
327
337
  let historyIdx = -1;
328
338
  let currentRedraw = null;
329
- async function readLine(prompt, initial = "") {
339
+ async function readLine(opts) {
330
340
  const wasRaw = stdin.isRaw ?? false;
331
341
  setRaw(true);
332
342
  stdin.resume();
333
- let line = initial;
343
+ let line = opts.initial ?? "";
344
+ let cursor = line.length;
334
345
  let suggestion = "";
335
- let tabState = null;
346
+ let palSels = [0];
347
+ let palScrolls = [0];
348
+ let overlayRows = 0;
349
+ function prompt() {
350
+ return opts.getPrompt();
351
+ }
352
+ function showsInput() {
353
+ return opts.getChrome?.().showsInput !== false;
354
+ }
355
+ function slashMode() {
356
+ return showsInput() && line.startsWith("/");
357
+ }
358
+ function paletteView() {
359
+ return resolvePaletteView(line, palSels, palScrolls);
360
+ }
361
+ function syncPaletteState(view) {
362
+ for (let i = 0; i < view.columns.length; i++) {
363
+ palSels[i] = view.columns[i].selected;
364
+ palScrolls[i] = view.columns[i].scroll;
365
+ }
366
+ }
367
+ function resetPalette() {
368
+ palSels = [0];
369
+ palScrolls = [0];
370
+ }
371
+ function clearOverlay() {
372
+ stdout.write("\r\x1B[J");
373
+ overlayRows = 0;
374
+ }
375
+ function applyWindowKey(ev) {
376
+ if (!opts.onWindowKey) return false;
377
+ const r = opts.onWindowKey(ev);
378
+ if (r.action === "none") return false;
379
+ if (r.action === "redraw") redraw();
380
+ return true;
381
+ }
336
382
  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");
383
+ const chrome = opts.getChrome?.();
384
+ if (chrome && !chrome.showsInput) {
385
+ clearOverlay();
386
+ const foot = chrome.footerShortcuts || "";
387
+ stdout.write(foot);
388
+ stdout.write("\r");
389
+ return;
390
+ }
391
+ const p = prompt();
392
+ suggestion = "";
393
+ if (slashMode()) {
394
+ const view = paletteView();
395
+ syncPaletteState(view);
396
+ if (cursor === line.length) {
397
+ suggestion = paletteGhost(view);
398
+ }
399
+ } else {
400
+ const parsed = parseLine(line);
401
+ const candidates = getCompletions(parsed);
402
+ const first = candidates[0];
403
+ if (cursor === line.length && parsed.current && first && first !== parsed.current && first.startsWith(parsed.current)) {
404
+ suggestion = first.slice(parsed.current.length);
405
+ }
406
+ }
407
+ clearOverlay();
408
+ stdout.write(p + line);
409
+ if (suggestion) stdout.write(c.dim(suggestion));
410
+ if (slashMode()) {
411
+ const view = paletteView();
412
+ const indent = promptSlashColumn(p);
413
+ const list = formatPaletteCascadeLines(view, indent);
414
+ if (list.length > 0) {
415
+ stdout.write("\n" + list.join("\n"));
416
+ overlayRows = list.length;
417
+ stdout.write(`\x1B[${overlayRows}A`);
418
+ }
345
419
  }
420
+ const col = promptSlashColumn(p) + cursor + 1;
421
+ stdout.write(`\r\x1B[${col}G`);
346
422
  }
347
423
  redraw();
348
424
  currentRedraw = redraw;
349
425
  return new Promise((resolve) => {
426
+ const done = (value) => {
427
+ stdin.removeListener("data", handler);
428
+ setRaw(wasRaw);
429
+ currentRedraw = null;
430
+ resolve(value);
431
+ };
432
+ const finishLine = (value) => {
433
+ clearOverlay();
434
+ stdout.write(prompt() + value + "\r\n");
435
+ if (value.length > 0) {
436
+ history.push(value);
437
+ if (history.length > 100) history.shift();
438
+ }
439
+ historyIdx = history.length;
440
+ done({ kind: "line", value });
441
+ };
442
+ const movePalSel = (delta) => {
443
+ const view = paletteView();
444
+ if (view.hidden) return false;
445
+ const col = view.columns[view.active];
446
+ if (!col || col.items.length === 0) return false;
447
+ const len = col.items.length;
448
+ const cur = clampPaletteSelection(col.selected, len);
449
+ const next = ((cur + delta) % len + len) % len;
450
+ palSels[view.active] = next;
451
+ redraw();
452
+ return true;
453
+ };
350
454
  const handler = (chunk) => {
351
455
  let i = 0;
352
456
  while (i < chunk.length) {
@@ -354,28 +458,69 @@ async function readLine(prompt, initial = "") {
354
458
  const next = consumeEscapeSeq(chunk, i);
355
459
  if (next !== null) {
356
460
  const len = next - i;
461
+ if (len === 1) {
462
+ if (applyWindowKey({ type: "escape" })) {
463
+ i = next;
464
+ continue;
465
+ }
466
+ i = next;
467
+ continue;
468
+ }
469
+ if (!showsInput()) {
470
+ i = next;
471
+ continue;
472
+ }
357
473
  if (len === 3 && chunk[i + 1] === 91) {
358
474
  const fin = chunk[i + 2];
359
- if (fin === 65 && historyIdx > 0) {
360
- historyIdx--;
361
- line = history[historyIdx] ?? "";
362
- tabState = null;
363
- redraw();
475
+ if (fin === 65) {
476
+ if (slashMode() && movePalSel(-1)) {
477
+ i = next;
478
+ continue;
479
+ }
480
+ if (historyIdx > 0) {
481
+ historyIdx--;
482
+ line = history[historyIdx] ?? "";
483
+ cursor = line.length;
484
+ resetPalette();
485
+ redraw();
486
+ }
364
487
  } else if (fin === 66) {
488
+ if (slashMode() && movePalSel(1)) {
489
+ i = next;
490
+ continue;
491
+ }
365
492
  if (historyIdx < history.length - 1) {
366
493
  historyIdx++;
367
494
  line = history[historyIdx] ?? "";
368
- tabState = null;
495
+ cursor = line.length;
496
+ resetPalette();
369
497
  redraw();
370
498
  } else if (historyIdx === history.length - 1) {
371
499
  historyIdx = history.length;
372
500
  line = "";
373
- tabState = null;
501
+ cursor = 0;
502
+ resetPalette();
503
+ redraw();
504
+ }
505
+ } else if (fin === 67) {
506
+ if (cursor < line.length) {
507
+ cursor++;
508
+ redraw();
509
+ } else if (suggestion) {
510
+ line += suggestion;
511
+ cursor = line.length;
374
512
  redraw();
375
513
  }
376
- } else if (fin === 67 && suggestion) {
377
- line += suggestion;
378
- tabState = null;
514
+ } else if (fin === 68) {
515
+ if (cursor > 0) {
516
+ cursor--;
517
+ redraw();
518
+ }
519
+ } else if (fin === 72) {
520
+ cursor = 0;
521
+ redraw();
522
+ } else if (fin === 70) {
523
+ cursor = line.length;
379
524
  redraw();
380
525
  }
381
526
  }
@@ -386,133 +531,125 @@ async function readLine(prompt, initial = "") {
386
531
  const byte = chunk[i];
387
532
  i++;
388
533
  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();
534
+ if (!showsInput()) continue;
535
+ if (slashMode()) {
536
+ const view = paletteView();
537
+ syncPaletteState(view);
538
+ if (!view.hidden && view.columns.some((col) => col.items.length > 0) && activeNode(view)) {
539
+ const acc = commitSelection(line, view);
540
+ if (acc.submit) {
541
+ finishLine(acc.line);
542
+ return;
543
+ }
544
+ line = acc.line;
545
+ cursor = line.length;
546
+ const depth = Math.max(0, acc.line.trimEnd().split(/\s+/).length - 1);
547
+ while (palSels.length <= depth + 1) palSels.push(0);
548
+ while (palScrolls.length <= depth + 1) palScrolls.push(0);
549
+ palSels[depth + 1] = 0;
550
+ palScrolls[depth + 1] = 0;
551
+ redraw();
552
+ continue;
553
+ }
395
554
  }
396
- historyIdx = history.length;
397
- resolve(line);
555
+ finishLine(line);
398
556
  return;
399
557
  }
400
558
  if (byte === 3) {
401
- stdin.removeListener("data", handler);
402
- setRaw(wasRaw);
559
+ if (!showsInput()) {
560
+ if (applyWindowKey({ type: "escape" })) continue;
561
+ clearOverlay();
562
+ stdout.write("\r\n");
563
+ done({ kind: "cancel" });
564
+ return;
565
+ }
403
566
  if (line.length > 0) {
404
567
  line = "";
405
- tabState = null;
406
- stdout.write("\r\x1B[K" + prompt);
568
+ cursor = 0;
569
+ resetPalette();
570
+ redraw();
407
571
  continue;
408
572
  }
573
+ clearOverlay();
409
574
  stdout.write("\r\n");
410
- resolve(null);
575
+ done({ kind: "cancel" });
411
576
  return;
412
577
  }
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
- };
578
+ if (byte === 16) {
579
+ if (!showsInput()) continue;
580
+ if (!line.startsWith("/")) {
581
+ line = "/" + line;
582
+ cursor = Math.min(cursor + 1, line.length);
430
583
  }
431
- const match = tabState.candidates[tabState.idx];
432
- line = line.slice(0, tabState.wordStart) + match;
433
- tabState.completedLine = line;
584
+ resetPalette();
434
585
  redraw();
435
586
  continue;
436
587
  }
588
+ if (byte === 9) {
589
+ if (slashMode()) {
590
+ movePalSel(1);
591
+ continue;
592
+ }
593
+ if (opts.cycleSendTarget()) {
594
+ redraw();
595
+ continue;
596
+ }
597
+ continue;
598
+ }
437
599
  if (byte === 12) {
438
- stdin.removeListener("data", handler);
439
- setRaw(wasRaw);
440
- resolve("__CTRLL__" + line);
600
+ clearOverlay();
601
+ done({ kind: "open-logs", pending: showsInput() ? line : "" });
441
602
  return;
442
603
  }
443
604
  if (byte === 127 || byte === 8) {
444
- if (line.length > 0) {
445
- line = line.slice(0, -1);
605
+ if (!showsInput()) continue;
606
+ if (cursor > 0) {
607
+ line = line.slice(0, cursor - 1) + line.slice(cursor);
608
+ cursor--;
609
+ }
610
+ if (slashMode()) {
611
+ const view = paletteView();
612
+ palSels[view.active] = 0;
613
+ palScrolls[view.active] = 0;
446
614
  }
447
- tabState = null;
448
615
  redraw();
449
616
  continue;
450
617
  }
451
618
  if (byte >= 32 && byte <= 126) {
452
- line += String.fromCharCode(byte);
453
- tabState = null;
619
+ const ch = String.fromCharCode(byte);
620
+ if (!showsInput()) {
621
+ applyWindowKey({ type: "char", ch });
622
+ continue;
623
+ }
624
+ let insert = ch;
625
+ const autoSlash = typeof opts.autoSlash === "function" ? opts.autoSlash() : opts.autoSlash;
626
+ if (autoSlash && line.length === 0 && insert !== "/") {
627
+ insert = "/" + insert;
628
+ }
629
+ line = line.slice(0, cursor) + insert + line.slice(cursor);
630
+ cursor += insert.length;
631
+ if (slashMode()) {
632
+ const view = paletteView();
633
+ palSels[view.active] = 0;
634
+ palScrolls[view.active] = 0;
635
+ }
454
636
  redraw();
455
637
  continue;
456
638
  }
457
639
  }
458
640
  };
459
641
  stdin.on("data", handler);
460
- }).finally(() => {
461
- currentRedraw = null;
462
642
  });
463
643
  }
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
644
  const SOURCE_ITEMS = SOURCE_META.map((m) => ({
472
- label: m.paint(m.name),
645
+ label: m.paint(`[${m.name}]`),
473
646
  value: m.value
474
647
  }));
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;
648
+ let openLogsWindowHook = null;
649
+ function writeLogLineToTty(wrapped) {
513
650
  pauseAllProgress();
514
651
  try {
515
- stdout.write(`\r\x1B[K${wrapLogLine(formatLog(log), logPrefixWidth(log))}
652
+ stdout.write(`\r\x1B[K${wrapped}
516
653
  `);
517
654
  } finally {
518
655
  resumeAllProgress();
@@ -521,17 +658,27 @@ function pushAndRender(log, filter) {
521
658
  }
522
659
  async function startRepl() {
523
660
  let stopping = false;
661
+ const onSigint = () => {
662
+ stdout.write(c.yellow("\n" + t("repl.forceStop") + "\n"));
663
+ forceStopAll();
664
+ stopRemoteAgent();
665
+ process.exit(130);
666
+ };
524
667
  const shutdown = async () => {
525
668
  if (stopping) return;
526
669
  stopping = true;
670
+ process.off("SIGINT", onSigint);
671
+ openLogsWindowHook = null;
527
672
  stdout.write(c.dim(t("repl.stopping") + "\n"));
528
673
  await stopAll();
674
+ stopRemoteAgent();
675
+ try {
676
+ setRaw(false);
677
+ stdin.pause();
678
+ } catch {
679
+ }
529
680
  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);
681
+ process.exit(0);
535
682
  };
536
683
  process.on("SIGINT", onSigint);
537
684
  if (!stdin.isTTY) {
@@ -552,107 +699,226 @@ async function startRepl() {
552
699
  await execCmd(p);
553
700
  }
554
701
  await shutdown();
555
- process.off("SIGINT", onSigint);
556
702
  return;
557
703
  }
558
704
  console.clear();
559
- stdout.write(welcome);
560
- let filter = { levels: [], sources: [] };
561
- const unsub = onLog((log) => pushAndRender(log, filter));
705
+ await playWelcomeAnimation();
706
+ let logsFilterState = { levels: [], sources: [] };
707
+ let preferredTarget = null;
708
+ let activeTargets = [];
709
+ let pendingInput = "";
710
+ const host = new WindowHost({
711
+ writeLog: writeLogLineToTty,
712
+ onChromeChange: () => currentRedraw?.()
713
+ });
714
+ function clearTerminal() {
715
+ console.clear();
716
+ stdout.write("\x1B[3J");
717
+ }
718
+ function seedWindowBuffer(w) {
719
+ const lines = [];
720
+ if (w.id === "logs") {
721
+ const f = w.getReplayFilter?.() ?? { levels: [], sources: [] };
722
+ const disk = readDiskLogs({
723
+ levels: f.levels,
724
+ sources: f.sources
725
+ });
726
+ for (const log of disk) {
727
+ if (!w.acceptLog(log)) continue;
728
+ const { text, indent } = w.formatLogLine(log);
729
+ lines.push(wrapLogLine(text, indent));
730
+ }
731
+ } else {
732
+ for (const log of getAllLogs()) {
733
+ if (!w.acceptLog(log)) continue;
734
+ const { text, indent } = w.formatLogLine(log);
735
+ lines.push(wrapLogLine(text, indent));
736
+ }
737
+ }
738
+ host.setBuffer(w.id, lines);
739
+ return lines;
740
+ }
741
+ function showActiveWindow(opts) {
742
+ clearTerminal();
743
+ const w = host.getActive();
744
+ if (!w) {
745
+ currentRedraw?.();
746
+ return;
747
+ }
748
+ let lines;
749
+ if (w.id === "logs") {
750
+ if (opts?.reseed) host.invalidateBuffer(w.id);
751
+ lines = host.hasBuffer(w.id) ? host.getBuffer(w.id) : seedWindowBuffer(w);
752
+ } else {
753
+ host.invalidateBuffer(w.id);
754
+ lines = seedWindowBuffer(w);
755
+ }
756
+ for (const l of lines) stdout.write(l + "\n");
757
+ currentRedraw?.();
758
+ }
759
+ host.register(
760
+ createLogsFilterWindow({
761
+ getState: () => logsFilterState,
762
+ setState: (next) => {
763
+ logsFilterState = next;
764
+ },
765
+ /* 筛选变更:作废 /logs 缓存并重新读盘一次 */
766
+ onFilterChanged: () => showActiveWindow({ reseed: true })
767
+ })
768
+ );
769
+ function syncServiceWindows(targets) {
770
+ for (const name of targets) {
771
+ const id = serviceWindowId(name);
772
+ if (!host.has(id)) host.register(createServiceWindow(name));
773
+ }
774
+ host.setServiceOrder(targets.map(serviceWindowId));
775
+ const svc = host.getActive()?.serviceName;
776
+ if (svc) preferredTarget = svc;
777
+ }
778
+ function openLogsWindow() {
779
+ host.open("logs");
780
+ showActiveWindow();
781
+ }
782
+ openLogsWindowHook = openLogsWindow;
783
+ const unsub = onLog((log) => {
784
+ if (host.getActive()) {
785
+ host.routeLog(log);
786
+ return;
787
+ }
788
+ writeLogLineToTty(wrapLogLine(formatLog(log), logPrefixWidth(log)));
789
+ });
790
+ async function refreshTargetsFromRuntime() {
791
+ const prevId = host.getActiveId();
792
+ activeTargets = await listActiveSendTargets();
793
+ syncServiceWindows(activeTargets);
794
+ if (preferredTarget && !activeTargets.includes(preferredTarget)) {
795
+ preferredTarget = activeTargets[0] ?? null;
796
+ }
797
+ const nextId = host.getActiveId();
798
+ if (nextId && (nextId !== prevId || !host.hasBuffer(nextId))) {
799
+ showActiveWindow();
800
+ } else if (!nextId && prevId) {
801
+ clearTerminal();
802
+ currentRedraw?.();
803
+ } else {
804
+ currentRedraw?.();
805
+ }
806
+ }
807
+ const unsubState = onServiceStateChange(() => {
808
+ void refreshTargetsFromRuntime();
809
+ });
562
810
  function onResize() {
563
811
  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();
812
+ const id = host.getActiveId();
813
+ if (id) host.invalidateBuffer(id);
814
+ showActiveWindow();
581
815
  }
582
816
  process.stdout.on("resize", onResize);
583
- let pendingInput = "";
817
+ function currentTarget() {
818
+ if (activeTargets.length === 0) return null;
819
+ const fromWin = host.getActive()?.serviceName;
820
+ if (fromWin && activeTargets.includes(fromWin)) {
821
+ preferredTarget = fromWin;
822
+ return fromWin;
823
+ }
824
+ if (preferredTarget && activeTargets.includes(preferredTarget)) return preferredTarget;
825
+ preferredTarget = activeTargets[0];
826
+ return preferredTarget;
827
+ }
828
+ function buildPrompt() {
829
+ const tgt = currentTarget();
830
+ return tgt ? paintSendPrompt(tgt) : plainPrompt();
831
+ }
584
832
  while (true) {
585
- const raw = await readLine(c.text(" \u276F "), pendingInput);
833
+ await refreshTargetsFromRuntime();
834
+ const result = await readLine({
835
+ getPrompt: buildPrompt,
836
+ /* 无服务时默认加 /(与窗口系统前行为一致);用 getter 以便进程退出后即时生效 */
837
+ autoSlash: () => activeTargets.length === 0,
838
+ getChrome: () => host.getChrome(),
839
+ onWindowKey: (ev) => {
840
+ const r = host.onKey(ev);
841
+ if (r.action === "redraw") showActiveWindow();
842
+ return r;
843
+ },
844
+ cycleSendTarget: () => {
845
+ const name = host.cycleServiceWindows();
846
+ if (!name) return false;
847
+ preferredTarget = name;
848
+ showActiveWindow();
849
+ return true;
850
+ },
851
+ initial: pendingInput
852
+ });
586
853
  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
- }
854
+ if (result.kind === "cancel") break;
855
+ if (result.kind === "open-logs") {
856
+ pendingInput = result.pending;
857
+ openLogsWindow();
858
+ continue;
859
+ }
860
+ if (result.kind === "run") {
861
+ try {
862
+ await execCmd(result.parts);
863
+ } catch (e) {
864
+ if (e === "QUIT") break;
865
+ console.log(c.red(t("common.error", { message: e.message })));
607
866
  }
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
867
  continue;
613
868
  }
614
- const trimmed = raw.trim();
869
+ const trimmed = result.value.trim();
615
870
  if (!trimmed) continue;
616
871
  try {
617
- await execCmd(trimmed.split(/\s+/));
872
+ if (trimmed.startsWith("/")) {
873
+ await execCmd(trimmed.split(/\s+/));
874
+ } else {
875
+ const tgt = currentTarget();
876
+ if (!tgt) {
877
+ await execCmd(("/" + trimmed).split(/\s+/));
878
+ } else {
879
+ const sent = await cmdSend(tgt, trimmed);
880
+ if (sent) stdout.write(sent + "\n");
881
+ }
882
+ }
618
883
  } catch (e) {
619
884
  if (e === "QUIT") break;
620
885
  console.log(c.red(t("common.error", { message: e.message })));
621
886
  }
622
887
  }
623
888
  process.stdout.off("resize", onResize);
889
+ unsubState();
624
890
  unsub();
625
891
  await shutdown();
626
- process.off("SIGINT", onSigint);
627
892
  }
628
893
  async function execCmd(parts) {
629
- const [cmd, ...args] = parts;
894
+ const normalized = parts.map((p, i) => i === 0 && p.startsWith("/") ? p.slice(1) : p);
895
+ const [cmd, ...args] = normalized;
896
+ if (cmd && !["help", "h", "?", "version"].includes(cmd)) {
897
+ const topGate = gateTopLevel(cmd, REPL_MODE);
898
+ if (topGate) {
899
+ stdout.write(topGate + "\n");
900
+ return;
901
+ }
902
+ }
630
903
  switch (cmd) {
631
904
  case "help":
632
905
  case "h":
633
906
  case "?":
634
- stdout.write(getHelp());
907
+ stdout.write(getHelp(REPL_MODE));
635
908
  break;
636
909
  case "version":
637
- stdout.write(`${version}
638
- `);
639
- break;
640
- case "locale":
641
- case "lang":
642
- stdout.write(cmdLocale(args) + "\n");
910
+ await playWelcomeAnimation();
643
911
  break;
644
912
  case "status":
645
- stdout.write(cmdStatus() + "\n");
913
+ stdout.write(await cmdStatus() + "\n");
646
914
  break;
647
915
  case "logs":
648
916
  case "log": {
649
- const out = cmdLogs(args, () => {
650
- if (!stdin.isTTY) {
651
- stdout.write(c.yellow(t("repl.followRequiresTty") + "\n"));
652
- return;
653
- }
654
- });
655
- if (out) stdout.write(out + "\n");
917
+ if (openLogsWindowHook) {
918
+ openLogsWindowHook();
919
+ break;
920
+ }
921
+ stdout.write(c.yellow(t("repl.logsReplOnly") + "\n"));
656
922
  break;
657
923
  }
658
924
  case "start":
@@ -675,31 +941,33 @@ async function execCmd(parts) {
675
941
  case "send": {
676
942
  const svc = args[0] ?? "";
677
943
  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();
944
+ {
945
+ const sent = await cmdSend(svc, msg);
946
+ if (sent) stdout.write(sent + "\n");
947
+ }
684
948
  break;
685
949
  }
686
950
  case "update":
687
- await cmdUpdate(args);
951
+ stdout.write(await cmdUpdate(args) + "\n");
688
952
  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");
953
+ case "locale":
954
+ case "lang":
955
+ stdout.write(cmdLocale(args) + "\n");
956
+ break;
957
+ case "debug":
958
+ stdout.write(await cmdDebug(args) + "\n");
959
+ break;
960
+ case "remote":
961
+ stdout.write(await cmdRemote(args, { daemonAfterEnroll: false }) + "\n");
962
+ break;
963
+ case "init": {
964
+ const wasRaw = stdin.isRaw ?? false;
965
+ setRaw(false);
966
+ try {
967
+ const { runWizard } = await import("./wizard.js");
968
+ await runWizard();
969
+ } finally {
970
+ setRaw(wasRaw);
703
971
  }
704
972
  break;
705
973
  }
@@ -707,24 +975,46 @@ async function execCmd(parts) {
707
975
  case "exit":
708
976
  case "q":
709
977
  throw "QUIT";
710
- default:
978
+ default: {
979
+ const modShort = resolveModuleTopShorthand(cmd);
980
+ if (modShort) {
981
+ const g = gateModuleSub(modShort, REPL_MODE);
982
+ if (g) {
983
+ stdout.write(g + "\n");
984
+ break;
985
+ }
986
+ stdout.write(await dispatchModuleCommand(modShort, args) + "\n");
987
+ break;
988
+ }
711
989
  if (isModuleCommand(cmd)) {
712
990
  const [sub, ...subRest] = args;
991
+ const g = gateModuleSub(sub, REPL_MODE);
992
+ if (g) {
993
+ stdout.write(g + "\n");
994
+ break;
995
+ }
713
996
  stdout.write(await dispatchModuleCommand(sub, subRest) + "\n");
714
997
  break;
715
998
  }
716
999
  if (isPacksCommand(cmd)) {
717
1000
  const [sub, ...subRest] = args;
1001
+ const g = gatePacksSub(sub, REPL_MODE);
1002
+ if (g) {
1003
+ stdout.write(g + "\n");
1004
+ break;
1005
+ }
718
1006
  stdout.write(await dispatchPacksCommand(sub, subRest) + "\n");
719
1007
  break;
720
1008
  }
721
1009
  stdout.write(c.yellow(t("common.unknownShort", { cmd: cmd ?? "" }) + "\n"));
1010
+ }
722
1011
  }
723
1012
  }
724
1013
  export {
725
1014
  HELP,
726
1015
  SOURCE_ITEMS,
727
1016
  getHelp,
1017
+ playWelcomeAnimation,
728
1018
  startRepl
729
1019
  };
730
1020
  //# sourceMappingURL=repl.js.map