@sfmc-bds/cli 0.2.0-beta.1 → 0.2.0-beta.6

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 +92 -0
  18. package/dist/command-surface.d.ts.map +1 -0
  19. package/dist/command-surface.js +350 -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 +168 -0
  34. package/dist/help-text.js.map +7 -0
  35. package/dist/i18n/locales/en.d.ts +101 -20
  36. package/dist/i18n/locales/en.d.ts.map +1 -1
  37. package/dist/i18n/locales/en.js +105 -20
  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 +153 -62
  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 +29 -33
  53. package/dist/module-commands.d.ts.map +1 -1
  54. package/dist/module-commands.js +101 -37
  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/module-wizard.d.ts +4 -6
  69. package/dist/module-wizard.d.ts.map +1 -1
  70. package/dist/module-wizard.js +108 -124
  71. package/dist/module-wizard.js.map +2 -2
  72. package/dist/pack-lifecycle.d.ts +1 -1
  73. package/dist/pack-lifecycle.d.ts.map +1 -1
  74. package/dist/pack-lifecycle.js +3 -3
  75. package/dist/pack-lifecycle.js.map +2 -2
  76. package/dist/pack-update/index.d.ts +18 -0
  77. package/dist/pack-update/index.d.ts.map +1 -0
  78. package/dist/pack-update/index.js +113 -0
  79. package/dist/pack-update/index.js.map +7 -0
  80. package/dist/registry.d.ts +6 -3
  81. package/dist/registry.d.ts.map +1 -1
  82. package/dist/registry.js +15 -3
  83. package/dist/registry.js.map +2 -2
  84. package/dist/remote-agent.d.ts.map +1 -1
  85. package/dist/remote-agent.js +7 -5
  86. package/dist/remote-agent.js.map +2 -2
  87. package/dist/repl-windows/format-display.d.ts +10 -0
  88. package/dist/repl-windows/format-display.d.ts.map +1 -0
  89. package/dist/repl-windows/format-display.js +57 -0
  90. package/dist/repl-windows/format-display.js.map +7 -0
  91. package/dist/repl-windows/host.d.ts +60 -0
  92. package/dist/repl-windows/host.d.ts.map +1 -0
  93. package/dist/repl-windows/host.js +179 -0
  94. package/dist/repl-windows/host.js.map +7 -0
  95. package/dist/repl-windows/index.d.ts +9 -0
  96. package/dist/repl-windows/index.d.ts.map +1 -0
  97. package/dist/repl-windows/index.js +13 -0
  98. package/dist/repl-windows/index.js.map +7 -0
  99. package/dist/repl-windows/logs-filter-window.d.ts +15 -0
  100. package/dist/repl-windows/logs-filter-window.d.ts.map +1 -0
  101. package/dist/repl-windows/logs-filter-window.js +77 -0
  102. package/dist/repl-windows/logs-filter-window.js.map +7 -0
  103. package/dist/repl-windows/service-window.d.ts +4 -0
  104. package/dist/repl-windows/service-window.d.ts.map +1 -0
  105. package/dist/repl-windows/service-window.js +28 -0
  106. package/dist/repl-windows/service-window.js.map +7 -0
  107. package/dist/repl-windows/types.d.ts +56 -0
  108. package/dist/repl-windows/types.d.ts.map +1 -0
  109. package/dist/repl-windows/types.js +1 -0
  110. package/dist/repl-windows/types.js.map +7 -0
  111. package/dist/repl.d.ts +7 -8
  112. package/dist/repl.d.ts.map +1 -1
  113. package/dist/repl.js +722 -412
  114. package/dist/repl.js.map +3 -3
  115. package/dist/runtime.d.ts +2 -0
  116. package/dist/runtime.d.ts.map +1 -1
  117. package/dist/runtime.js +4 -0
  118. package/dist/runtime.js.map +2 -2
  119. package/dist/send-target.d.ts +11 -0
  120. package/dist/send-target.d.ts.map +1 -0
  121. package/dist/send-target.js +38 -0
  122. package/dist/send-target.js.map +7 -0
  123. package/dist/services.d.ts +21 -1
  124. package/dist/services.d.ts.map +1 -1
  125. package/dist/services.js +146 -7
  126. package/dist/services.js.map +2 -2
  127. package/dist/theme.js +1 -1
  128. package/dist/theme.js.map +2 -2
  129. package/dist/wizard.d.ts.map +1 -1
  130. package/dist/wizard.js +11 -9
  131. package/dist/wizard.js.map +2 -2
  132. package/dist/world-packs.d.ts.map +1 -1
  133. package/dist/world-packs.js +6 -3
  134. package/dist/world-packs.js.map +2 -2
  135. package/package.json +16 -16
  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);
@@ -212,11 +310,6 @@ function getCompletions(parsed) {
212
310
  return ["--build-only"].filter(sw);
213
311
  }
214
312
  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
313
  return ["--from"].filter(sw);
221
314
  }
222
315
  if (argIndex >= 1 && verb === "install") {
@@ -246,107 +339,124 @@ function consumeEscapeSeq(chunk, i) {
246
339
  if (rem >= 1) return i + 2;
247
340
  return i + 1;
248
341
  }
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
342
  const history = [];
327
343
  let historyIdx = -1;
328
344
  let currentRedraw = null;
329
- async function readLine(prompt, initial = "") {
345
+ async function readLine(opts) {
330
346
  const wasRaw = stdin.isRaw ?? false;
331
347
  setRaw(true);
332
348
  stdin.resume();
333
- let line = initial;
349
+ let line = opts.initial ?? "";
350
+ let cursor = line.length;
334
351
  let suggestion = "";
335
- let tabState = null;
352
+ let palSels = [0];
353
+ let palScrolls = [0];
354
+ let overlayRows = 0;
355
+ function prompt() {
356
+ return opts.getPrompt();
357
+ }
358
+ function showsInput() {
359
+ return opts.getChrome?.().showsInput !== false;
360
+ }
361
+ function slashMode() {
362
+ return showsInput() && line.startsWith("/");
363
+ }
364
+ function paletteView() {
365
+ return resolvePaletteView(line, palSels, palScrolls);
366
+ }
367
+ function syncPaletteState(view) {
368
+ for (let i = 0; i < view.columns.length; i++) {
369
+ palSels[i] = view.columns[i].selected;
370
+ palScrolls[i] = view.columns[i].scroll;
371
+ }
372
+ }
373
+ function resetPalette() {
374
+ palSels = [0];
375
+ palScrolls = [0];
376
+ }
377
+ function clearOverlay() {
378
+ stdout.write("\r\x1B[J");
379
+ overlayRows = 0;
380
+ }
381
+ function applyWindowKey(ev) {
382
+ if (!opts.onWindowKey) return false;
383
+ const r = opts.onWindowKey(ev);
384
+ if (r.action === "none") return false;
385
+ if (r.action === "redraw") redraw();
386
+ return true;
387
+ }
336
388
  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");
389
+ const chrome = opts.getChrome?.();
390
+ if (chrome && !chrome.showsInput) {
391
+ clearOverlay();
392
+ const foot = chrome.footerShortcuts || "";
393
+ stdout.write(foot);
394
+ stdout.write("\r");
395
+ return;
396
+ }
397
+ const p = prompt();
398
+ suggestion = "";
399
+ if (slashMode()) {
400
+ const view = paletteView();
401
+ syncPaletteState(view);
402
+ if (cursor === line.length) {
403
+ suggestion = paletteGhost(view);
404
+ }
405
+ } else {
406
+ const parsed = parseLine(line);
407
+ const candidates = getCompletions(parsed);
408
+ const first = candidates[0];
409
+ if (cursor === line.length && parsed.current && first && first !== parsed.current && first.startsWith(parsed.current)) {
410
+ suggestion = first.slice(parsed.current.length);
411
+ }
412
+ }
413
+ clearOverlay();
414
+ stdout.write(p + line);
415
+ if (suggestion) stdout.write(c.dim(suggestion));
416
+ if (slashMode()) {
417
+ const view = paletteView();
418
+ const indent = promptSlashColumn(p);
419
+ const list = formatPaletteCascadeLines(view, indent);
420
+ if (list.length > 0) {
421
+ stdout.write("\n" + list.join("\n"));
422
+ overlayRows = list.length;
423
+ stdout.write(`\x1B[${overlayRows}A`);
424
+ }
345
425
  }
426
+ const col = promptSlashColumn(p) + cursor + 1;
427
+ stdout.write(`\r\x1B[${col}G`);
346
428
  }
347
429
  redraw();
348
430
  currentRedraw = redraw;
349
431
  return new Promise((resolve) => {
432
+ const done = (value) => {
433
+ stdin.removeListener("data", handler);
434
+ setRaw(wasRaw);
435
+ currentRedraw = null;
436
+ resolve(value);
437
+ };
438
+ const finishLine = (value) => {
439
+ clearOverlay();
440
+ stdout.write(prompt() + value + "\r\n");
441
+ if (value.length > 0) {
442
+ history.push(value);
443
+ if (history.length > 100) history.shift();
444
+ }
445
+ historyIdx = history.length;
446
+ done({ kind: "line", value });
447
+ };
448
+ const movePalSel = (delta) => {
449
+ const view = paletteView();
450
+ if (view.hidden) return false;
451
+ const col = view.columns[view.active];
452
+ if (!col || col.items.length === 0) return false;
453
+ const len = col.items.length;
454
+ const cur = clampPaletteSelection(col.selected, len);
455
+ const next = ((cur + delta) % len + len) % len;
456
+ palSels[view.active] = next;
457
+ redraw();
458
+ return true;
459
+ };
350
460
  const handler = (chunk) => {
351
461
  let i = 0;
352
462
  while (i < chunk.length) {
@@ -354,28 +464,69 @@ async function readLine(prompt, initial = "") {
354
464
  const next = consumeEscapeSeq(chunk, i);
355
465
  if (next !== null) {
356
466
  const len = next - i;
467
+ if (len === 1) {
468
+ if (applyWindowKey({ type: "escape" })) {
469
+ i = next;
470
+ continue;
471
+ }
472
+ i = next;
473
+ continue;
474
+ }
475
+ if (!showsInput()) {
476
+ i = next;
477
+ continue;
478
+ }
357
479
  if (len === 3 && chunk[i + 1] === 91) {
358
480
  const fin = chunk[i + 2];
359
- if (fin === 65 && historyIdx > 0) {
360
- historyIdx--;
361
- line = history[historyIdx] ?? "";
362
- tabState = null;
363
- redraw();
481
+ if (fin === 65) {
482
+ if (slashMode() && movePalSel(-1)) {
483
+ i = next;
484
+ continue;
485
+ }
486
+ if (historyIdx > 0) {
487
+ historyIdx--;
488
+ line = history[historyIdx] ?? "";
489
+ cursor = line.length;
490
+ resetPalette();
491
+ redraw();
492
+ }
364
493
  } else if (fin === 66) {
494
+ if (slashMode() && movePalSel(1)) {
495
+ i = next;
496
+ continue;
497
+ }
365
498
  if (historyIdx < history.length - 1) {
366
499
  historyIdx++;
367
500
  line = history[historyIdx] ?? "";
368
- tabState = null;
501
+ cursor = line.length;
502
+ resetPalette();
369
503
  redraw();
370
504
  } else if (historyIdx === history.length - 1) {
371
505
  historyIdx = history.length;
372
506
  line = "";
373
- tabState = null;
507
+ cursor = 0;
508
+ resetPalette();
509
+ redraw();
510
+ }
511
+ } else if (fin === 67) {
512
+ if (cursor < line.length) {
513
+ cursor++;
514
+ redraw();
515
+ } else if (suggestion) {
516
+ line += suggestion;
517
+ cursor = line.length;
518
+ redraw();
519
+ }
520
+ } else if (fin === 68) {
521
+ if (cursor > 0) {
522
+ cursor--;
374
523
  redraw();
375
524
  }
376
- } else if (fin === 67 && suggestion) {
377
- line += suggestion;
378
- tabState = null;
525
+ } else if (fin === 72) {
526
+ cursor = 0;
527
+ redraw();
528
+ } else if (fin === 70) {
529
+ cursor = line.length;
379
530
  redraw();
380
531
  }
381
532
  }
@@ -386,133 +537,125 @@ async function readLine(prompt, initial = "") {
386
537
  const byte = chunk[i];
387
538
  i++;
388
539
  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();
540
+ if (!showsInput()) continue;
541
+ if (slashMode()) {
542
+ const view = paletteView();
543
+ syncPaletteState(view);
544
+ if (!view.hidden && view.columns.some((col) => col.items.length > 0) && activeNode(view)) {
545
+ const acc = commitSelection(line, view);
546
+ if (acc.submit) {
547
+ finishLine(acc.line);
548
+ return;
549
+ }
550
+ line = acc.line;
551
+ cursor = line.length;
552
+ const depth = Math.max(0, acc.line.trimEnd().split(/\s+/).length - 1);
553
+ while (palSels.length <= depth + 1) palSels.push(0);
554
+ while (palScrolls.length <= depth + 1) palScrolls.push(0);
555
+ palSels[depth + 1] = 0;
556
+ palScrolls[depth + 1] = 0;
557
+ redraw();
558
+ continue;
559
+ }
395
560
  }
396
- historyIdx = history.length;
397
- resolve(line);
561
+ finishLine(line);
398
562
  return;
399
563
  }
400
564
  if (byte === 3) {
401
- stdin.removeListener("data", handler);
402
- setRaw(wasRaw);
565
+ if (!showsInput()) {
566
+ if (applyWindowKey({ type: "escape" })) continue;
567
+ clearOverlay();
568
+ stdout.write("\r\n");
569
+ done({ kind: "cancel" });
570
+ return;
571
+ }
403
572
  if (line.length > 0) {
404
573
  line = "";
405
- tabState = null;
406
- stdout.write("\r\x1B[K" + prompt);
574
+ cursor = 0;
575
+ resetPalette();
576
+ redraw();
407
577
  continue;
408
578
  }
579
+ clearOverlay();
409
580
  stdout.write("\r\n");
410
- resolve(null);
581
+ done({ kind: "cancel" });
411
582
  return;
412
583
  }
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
- };
584
+ if (byte === 16) {
585
+ if (!showsInput()) continue;
586
+ if (!line.startsWith("/")) {
587
+ line = "/" + line;
588
+ cursor = Math.min(cursor + 1, line.length);
430
589
  }
431
- const match = tabState.candidates[tabState.idx];
432
- line = line.slice(0, tabState.wordStart) + match;
433
- tabState.completedLine = line;
590
+ resetPalette();
434
591
  redraw();
435
592
  continue;
436
593
  }
594
+ if (byte === 9) {
595
+ if (slashMode()) {
596
+ movePalSel(1);
597
+ continue;
598
+ }
599
+ if (opts.cycleSendTarget()) {
600
+ redraw();
601
+ continue;
602
+ }
603
+ continue;
604
+ }
437
605
  if (byte === 12) {
438
- stdin.removeListener("data", handler);
439
- setRaw(wasRaw);
440
- resolve("__CTRLL__" + line);
606
+ clearOverlay();
607
+ done({ kind: "open-logs", pending: showsInput() ? line : "" });
441
608
  return;
442
609
  }
443
610
  if (byte === 127 || byte === 8) {
444
- if (line.length > 0) {
445
- line = line.slice(0, -1);
611
+ if (!showsInput()) continue;
612
+ if (cursor > 0) {
613
+ line = line.slice(0, cursor - 1) + line.slice(cursor);
614
+ cursor--;
615
+ }
616
+ if (slashMode()) {
617
+ const view = paletteView();
618
+ palSels[view.active] = 0;
619
+ palScrolls[view.active] = 0;
446
620
  }
447
- tabState = null;
448
621
  redraw();
449
622
  continue;
450
623
  }
451
624
  if (byte >= 32 && byte <= 126) {
452
- line += String.fromCharCode(byte);
453
- tabState = null;
625
+ const ch = String.fromCharCode(byte);
626
+ if (!showsInput()) {
627
+ applyWindowKey({ type: "char", ch });
628
+ continue;
629
+ }
630
+ let insert = ch;
631
+ const autoSlash = typeof opts.autoSlash === "function" ? opts.autoSlash() : opts.autoSlash;
632
+ if (autoSlash && line.length === 0 && insert !== "/") {
633
+ insert = "/" + insert;
634
+ }
635
+ line = line.slice(0, cursor) + insert + line.slice(cursor);
636
+ cursor += insert.length;
637
+ if (slashMode()) {
638
+ const view = paletteView();
639
+ palSels[view.active] = 0;
640
+ palScrolls[view.active] = 0;
641
+ }
454
642
  redraw();
455
643
  continue;
456
644
  }
457
645
  }
458
646
  };
459
647
  stdin.on("data", handler);
460
- }).finally(() => {
461
- currentRedraw = null;
462
648
  });
463
649
  }
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
650
  const SOURCE_ITEMS = SOURCE_META.map((m) => ({
472
- label: m.paint(m.name),
651
+ label: m.paint(`[${m.name}]`),
473
652
  value: m.value
474
653
  }));
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;
654
+ let openLogsWindowHook = null;
655
+ function writeLogLineToTty(wrapped) {
513
656
  pauseAllProgress();
514
657
  try {
515
- stdout.write(`\r\x1B[K${wrapLogLine(formatLog(log), logPrefixWidth(log))}
658
+ stdout.write(`\r\x1B[K${wrapped}
516
659
  `);
517
660
  } finally {
518
661
  resumeAllProgress();
@@ -521,17 +664,27 @@ function pushAndRender(log, filter) {
521
664
  }
522
665
  async function startRepl() {
523
666
  let stopping = false;
667
+ const onSigint = () => {
668
+ stdout.write(c.yellow("\n" + t("repl.forceStop") + "\n"));
669
+ forceStopAll();
670
+ stopRemoteAgent();
671
+ process.exit(130);
672
+ };
524
673
  const shutdown = async () => {
525
674
  if (stopping) return;
526
675
  stopping = true;
676
+ process.off("SIGINT", onSigint);
677
+ openLogsWindowHook = null;
527
678
  stdout.write(c.dim(t("repl.stopping") + "\n"));
528
679
  await stopAll();
680
+ stopRemoteAgent();
681
+ try {
682
+ setRaw(false);
683
+ stdin.pause();
684
+ } catch {
685
+ }
529
686
  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);
687
+ process.exit(0);
535
688
  };
536
689
  process.on("SIGINT", onSigint);
537
690
  if (!stdin.isTTY) {
@@ -552,107 +705,226 @@ async function startRepl() {
552
705
  await execCmd(p);
553
706
  }
554
707
  await shutdown();
555
- process.off("SIGINT", onSigint);
556
708
  return;
557
709
  }
558
710
  console.clear();
559
- stdout.write(welcome);
560
- let filter = { levels: [], sources: [] };
561
- const unsub = onLog((log) => pushAndRender(log, filter));
711
+ await playWelcomeAnimation();
712
+ let logsFilterState = { levels: [], sources: [] };
713
+ let preferredTarget = null;
714
+ let activeTargets = [];
715
+ let pendingInput = "";
716
+ const host = new WindowHost({
717
+ writeLog: writeLogLineToTty,
718
+ onChromeChange: () => currentRedraw?.()
719
+ });
720
+ function clearTerminal() {
721
+ console.clear();
722
+ stdout.write("\x1B[3J");
723
+ }
724
+ function seedWindowBuffer(w) {
725
+ const lines = [];
726
+ if (w.id === "logs") {
727
+ const f = w.getReplayFilter?.() ?? { levels: [], sources: [] };
728
+ const disk = readDiskLogs({
729
+ levels: f.levels,
730
+ sources: f.sources
731
+ });
732
+ for (const log of disk) {
733
+ if (!w.acceptLog(log)) continue;
734
+ const { text, indent } = w.formatLogLine(log);
735
+ lines.push(wrapLogLine(text, indent));
736
+ }
737
+ } else {
738
+ for (const log of getAllLogs()) {
739
+ if (!w.acceptLog(log)) continue;
740
+ const { text, indent } = w.formatLogLine(log);
741
+ lines.push(wrapLogLine(text, indent));
742
+ }
743
+ }
744
+ host.setBuffer(w.id, lines);
745
+ return lines;
746
+ }
747
+ function showActiveWindow(opts) {
748
+ clearTerminal();
749
+ const w = host.getActive();
750
+ if (!w) {
751
+ currentRedraw?.();
752
+ return;
753
+ }
754
+ let lines;
755
+ if (w.id === "logs") {
756
+ if (opts?.reseed) host.invalidateBuffer(w.id);
757
+ lines = host.hasBuffer(w.id) ? host.getBuffer(w.id) : seedWindowBuffer(w);
758
+ } else {
759
+ host.invalidateBuffer(w.id);
760
+ lines = seedWindowBuffer(w);
761
+ }
762
+ for (const l of lines) stdout.write(l + "\n");
763
+ currentRedraw?.();
764
+ }
765
+ host.register(
766
+ createLogsFilterWindow({
767
+ getState: () => logsFilterState,
768
+ setState: (next) => {
769
+ logsFilterState = next;
770
+ },
771
+ /* 筛选变更:作废 /logs 缓存并重新读盘一次 */
772
+ onFilterChanged: () => showActiveWindow({ reseed: true })
773
+ })
774
+ );
775
+ function syncServiceWindows(targets) {
776
+ for (const name of targets) {
777
+ const id = serviceWindowId(name);
778
+ if (!host.has(id)) host.register(createServiceWindow(name));
779
+ }
780
+ host.setServiceOrder(targets.map(serviceWindowId));
781
+ const svc = host.getActive()?.serviceName;
782
+ if (svc) preferredTarget = svc;
783
+ }
784
+ function openLogsWindow() {
785
+ host.open("logs");
786
+ showActiveWindow();
787
+ }
788
+ openLogsWindowHook = openLogsWindow;
789
+ const unsub = onLog((log) => {
790
+ if (host.getActive()) {
791
+ host.routeLog(log);
792
+ return;
793
+ }
794
+ writeLogLineToTty(wrapLogLine(formatLog(log), logPrefixWidth(log)));
795
+ });
796
+ async function refreshTargetsFromRuntime() {
797
+ const prevId = host.getActiveId();
798
+ activeTargets = await listActiveSendTargets();
799
+ syncServiceWindows(activeTargets);
800
+ if (preferredTarget && !activeTargets.includes(preferredTarget)) {
801
+ preferredTarget = activeTargets[0] ?? null;
802
+ }
803
+ const nextId = host.getActiveId();
804
+ if (nextId && (nextId !== prevId || !host.hasBuffer(nextId))) {
805
+ showActiveWindow();
806
+ } else if (!nextId && prevId) {
807
+ clearTerminal();
808
+ currentRedraw?.();
809
+ } else {
810
+ currentRedraw?.();
811
+ }
812
+ }
813
+ const unsubState = onServiceStateChange(() => {
814
+ void refreshTargetsFromRuntime();
815
+ });
562
816
  function onResize() {
563
817
  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();
818
+ const id = host.getActiveId();
819
+ if (id) host.invalidateBuffer(id);
820
+ showActiveWindow();
581
821
  }
582
822
  process.stdout.on("resize", onResize);
583
- let pendingInput = "";
823
+ function currentTarget() {
824
+ if (activeTargets.length === 0) return null;
825
+ const fromWin = host.getActive()?.serviceName;
826
+ if (fromWin && activeTargets.includes(fromWin)) {
827
+ preferredTarget = fromWin;
828
+ return fromWin;
829
+ }
830
+ if (preferredTarget && activeTargets.includes(preferredTarget)) return preferredTarget;
831
+ preferredTarget = activeTargets[0];
832
+ return preferredTarget;
833
+ }
834
+ function buildPrompt() {
835
+ const tgt = currentTarget();
836
+ return tgt ? paintSendPrompt(tgt) : plainPrompt();
837
+ }
584
838
  while (true) {
585
- const raw = await readLine(c.text(" \u276F "), pendingInput);
839
+ await refreshTargetsFromRuntime();
840
+ const result = await readLine({
841
+ getPrompt: buildPrompt,
842
+ /* 无服务时默认加 /(与窗口系统前行为一致);用 getter 以便进程退出后即时生效 */
843
+ autoSlash: () => activeTargets.length === 0,
844
+ getChrome: () => host.getChrome(),
845
+ onWindowKey: (ev) => {
846
+ const r = host.onKey(ev);
847
+ if (r.action === "redraw") showActiveWindow();
848
+ return r;
849
+ },
850
+ cycleSendTarget: () => {
851
+ const name = host.cycleServiceWindows();
852
+ if (!name) return false;
853
+ preferredTarget = name;
854
+ showActiveWindow();
855
+ return true;
856
+ },
857
+ initial: pendingInput
858
+ });
586
859
  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
- }
860
+ if (result.kind === "cancel") break;
861
+ if (result.kind === "open-logs") {
862
+ pendingInput = result.pending;
863
+ openLogsWindow();
864
+ continue;
865
+ }
866
+ if (result.kind === "run") {
867
+ try {
868
+ await execCmd(result.parts);
869
+ } catch (e) {
870
+ if (e === "QUIT") break;
871
+ console.log(c.red(t("common.error", { message: e.message })));
607
872
  }
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
873
  continue;
613
874
  }
614
- const trimmed = raw.trim();
875
+ const trimmed = result.value.trim();
615
876
  if (!trimmed) continue;
616
877
  try {
617
- await execCmd(trimmed.split(/\s+/));
878
+ if (trimmed.startsWith("/")) {
879
+ await execCmd(trimmed.split(/\s+/));
880
+ } else {
881
+ const tgt = currentTarget();
882
+ if (!tgt) {
883
+ await execCmd(("/" + trimmed).split(/\s+/));
884
+ } else {
885
+ const sent = await cmdSend(tgt, trimmed);
886
+ if (sent) stdout.write(sent + "\n");
887
+ }
888
+ }
618
889
  } catch (e) {
619
890
  if (e === "QUIT") break;
620
891
  console.log(c.red(t("common.error", { message: e.message })));
621
892
  }
622
893
  }
623
894
  process.stdout.off("resize", onResize);
895
+ unsubState();
624
896
  unsub();
625
897
  await shutdown();
626
- process.off("SIGINT", onSigint);
627
898
  }
628
899
  async function execCmd(parts) {
629
- const [cmd, ...args] = parts;
900
+ const normalized = parts.map((p, i) => i === 0 && p.startsWith("/") ? p.slice(1) : p);
901
+ const [cmd, ...args] = normalized;
902
+ if (cmd && !["help", "h", "?", "version"].includes(cmd)) {
903
+ const topGate = gateTopLevel(cmd, REPL_MODE);
904
+ if (topGate) {
905
+ stdout.write(topGate + "\n");
906
+ return;
907
+ }
908
+ }
630
909
  switch (cmd) {
631
910
  case "help":
632
911
  case "h":
633
912
  case "?":
634
- stdout.write(getHelp());
913
+ stdout.write(getHelp(REPL_MODE));
635
914
  break;
636
915
  case "version":
637
- stdout.write(`${version}
638
- `);
639
- break;
640
- case "locale":
641
- case "lang":
642
- stdout.write(cmdLocale(args) + "\n");
916
+ await playWelcomeAnimation();
643
917
  break;
644
918
  case "status":
645
- stdout.write(cmdStatus() + "\n");
919
+ stdout.write(await cmdStatus() + "\n");
646
920
  break;
647
921
  case "logs":
648
922
  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");
923
+ if (openLogsWindowHook) {
924
+ openLogsWindowHook();
925
+ break;
926
+ }
927
+ stdout.write(c.yellow(t("repl.logsReplOnly") + "\n"));
656
928
  break;
657
929
  }
658
930
  case "start":
@@ -675,31 +947,33 @@ async function execCmd(parts) {
675
947
  case "send": {
676
948
  const svc = args[0] ?? "";
677
949
  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();
950
+ {
951
+ const sent = await cmdSend(svc, msg);
952
+ if (sent) stdout.write(sent + "\n");
953
+ }
684
954
  break;
685
955
  }
686
956
  case "update":
687
- await cmdUpdate(args);
957
+ stdout.write(await cmdUpdate(args) + "\n");
688
958
  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");
959
+ case "locale":
960
+ case "lang":
961
+ stdout.write(cmdLocale(args) + "\n");
962
+ break;
963
+ case "debug":
964
+ stdout.write(await cmdDebug(args) + "\n");
965
+ break;
966
+ case "remote":
967
+ stdout.write(await cmdRemote(args, { daemonAfterEnroll: false }) + "\n");
968
+ break;
969
+ case "init": {
970
+ const wasRaw = stdin.isRaw ?? false;
971
+ setRaw(false);
972
+ try {
973
+ const { runWizard } = await import("./wizard.js");
974
+ await runWizard();
975
+ } finally {
976
+ setRaw(wasRaw);
703
977
  }
704
978
  break;
705
979
  }
@@ -707,24 +981,60 @@ async function execCmd(parts) {
707
981
  case "exit":
708
982
  case "q":
709
983
  throw "QUIT";
710
- default:
984
+ default: {
985
+ const modShort = resolveModuleTopShorthand(cmd);
986
+ if (modShort) {
987
+ const g = gateModuleSub(modShort, REPL_MODE);
988
+ if (g) {
989
+ stdout.write(g + "\n");
990
+ break;
991
+ }
992
+ const needsCooked = modShort === "create" || modShort === "dev";
993
+ const wasRaw = stdin.isRaw ?? false;
994
+ if (needsCooked) setRaw(false);
995
+ try {
996
+ stdout.write(await dispatchModuleCommand(modShort, args) + "\n");
997
+ } finally {
998
+ if (needsCooked) setRaw(wasRaw);
999
+ }
1000
+ break;
1001
+ }
711
1002
  if (isModuleCommand(cmd)) {
712
1003
  const [sub, ...subRest] = args;
713
- stdout.write(await dispatchModuleCommand(sub, subRest) + "\n");
1004
+ const g = gateModuleSub(sub, REPL_MODE);
1005
+ if (g) {
1006
+ stdout.write(g + "\n");
1007
+ break;
1008
+ }
1009
+ const needsCooked = sub === "create" || sub === "dev";
1010
+ const wasRaw = stdin.isRaw ?? false;
1011
+ if (needsCooked) setRaw(false);
1012
+ try {
1013
+ stdout.write(await dispatchModuleCommand(sub, subRest) + "\n");
1014
+ } finally {
1015
+ if (needsCooked) setRaw(wasRaw);
1016
+ }
714
1017
  break;
715
1018
  }
716
1019
  if (isPacksCommand(cmd)) {
717
1020
  const [sub, ...subRest] = args;
1021
+ const g = gatePacksSub(sub, REPL_MODE);
1022
+ if (g) {
1023
+ stdout.write(g + "\n");
1024
+ break;
1025
+ }
718
1026
  stdout.write(await dispatchPacksCommand(sub, subRest) + "\n");
719
1027
  break;
720
1028
  }
721
1029
  stdout.write(c.yellow(t("common.unknownShort", { cmd: cmd ?? "" }) + "\n"));
1030
+ }
722
1031
  }
723
1032
  }
724
1033
  export {
725
1034
  HELP,
726
1035
  SOURCE_ITEMS,
727
1036
  getHelp,
1037
+ playWelcomeAnimation,
728
1038
  startRepl
729
1039
  };
730
1040
  //# sourceMappingURL=repl.js.map