@poolzin/pool-bot 2026.3.7 → 2026.3.10

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 (150) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +147 -69
  3. package/dist/.buildstamp +1 -1
  4. package/dist/agents/error-classifier.js +251 -0
  5. package/dist/agents/skills/security.js +211 -0
  6. package/dist/build-info.json +3 -3
  7. package/dist/cli/cron-cli/register.cron-dashboard.js +339 -0
  8. package/dist/cli/cron-cli/register.js +2 -0
  9. package/dist/cli/errors.js +187 -0
  10. package/dist/cli/lazy-commands.example.js +113 -0
  11. package/dist/cli/lazy-commands.js +329 -0
  12. package/dist/cli/program/command-registry.js +26 -0
  13. package/dist/cli/program/register.maintenance.js +21 -0
  14. package/dist/cli/program/register.skills.js +4 -0
  15. package/dist/cli/program/register.subclis.js +9 -0
  16. package/dist/cli/swarm-cli/register.js +8 -0
  17. package/dist/cli/swarm-cli/register.swarm-status.js +488 -0
  18. package/dist/cli/telemetry-cli/register.js +10 -0
  19. package/dist/cli/telemetry-cli/register.telemetry-alerts.js +176 -0
  20. package/dist/cli/telemetry-cli/register.telemetry-metrics.js +323 -0
  21. package/dist/cli/telemetry-cli/register.telemetry-status.js +179 -0
  22. package/dist/commands/doctor-checks.js +498 -0
  23. package/dist/config/config.js +1 -0
  24. package/dist/config/secrets-integration.js +88 -0
  25. package/dist/context-engine/index.js +33 -0
  26. package/dist/context-engine/legacy.js +179 -0
  27. package/dist/context-engine/registry.js +86 -0
  28. package/dist/context-engine/summarizing.js +290 -0
  29. package/dist/context-engine/types.js +7 -0
  30. package/dist/cron/service/timer.js +18 -0
  31. package/dist/gateway/protocol/index.js +5 -2
  32. package/dist/gateway/protocol/schema/error-codes.js +1 -0
  33. package/dist/gateway/protocol/schema/swarm.js +80 -0
  34. package/dist/gateway/protocol/schema.js +1 -0
  35. package/dist/gateway/server-close.js +4 -0
  36. package/dist/gateway/server-constants.js +1 -0
  37. package/dist/gateway/server-cron.js +29 -0
  38. package/dist/gateway/server-maintenance.js +35 -2
  39. package/dist/gateway/server-methods/swarm.js +58 -0
  40. package/dist/gateway/server-methods/telemetry.js +71 -0
  41. package/dist/gateway/server-methods-list.js +8 -0
  42. package/dist/gateway/server-methods.js +9 -2
  43. package/dist/gateway/server.impl.js +33 -16
  44. package/dist/infra/abort-pattern.js +106 -0
  45. package/dist/infra/retry.js +96 -0
  46. package/dist/secrets/index.js +28 -0
  47. package/dist/secrets/resolver.js +185 -0
  48. package/dist/secrets/runtime.js +142 -0
  49. package/dist/secrets/types.js +11 -0
  50. package/dist/security/dangerous-tools.js +80 -0
  51. package/dist/security/types.js +12 -0
  52. package/dist/skills/commands.js +333 -0
  53. package/dist/skills/index.js +164 -0
  54. package/dist/skills/loader.js +282 -0
  55. package/dist/skills/parser.js +446 -0
  56. package/dist/skills/registry.js +394 -0
  57. package/dist/skills/security.js +312 -0
  58. package/dist/skills/types.js +21 -0
  59. package/dist/swarm/service.js +247 -0
  60. package/dist/telemetry/alert-engine.js +258 -0
  61. package/dist/telemetry/cron-instrumentation.js +49 -0
  62. package/dist/telemetry/gateway-instrumentation.js +80 -0
  63. package/dist/telemetry/instrumentation.js +66 -0
  64. package/dist/telemetry/service.js +345 -0
  65. package/dist/test-utils/index.js +219 -0
  66. package/dist/tui/components/assistant-message.js +6 -2
  67. package/dist/tui/components/hyperlink-markdown.js +32 -0
  68. package/dist/tui/components/searchable-select-list.js +12 -1
  69. package/dist/tui/components/user-message.js +6 -2
  70. package/dist/tui/index.js +611 -0
  71. package/dist/tui/theme/theme-detection.js +226 -0
  72. package/dist/tui/tui-command-handlers.js +20 -0
  73. package/dist/tui/tui-formatters.js +4 -3
  74. package/dist/tui/utils/ctrl-c-handler.js +67 -0
  75. package/dist/tui/utils/osc8-hyperlinks.js +208 -0
  76. package/dist/tui/utils/safe-stop.js +180 -0
  77. package/dist/tui/utils/session-key-utils.js +81 -0
  78. package/dist/tui/utils/text-sanitization.js +284 -0
  79. package/dist/utils/lru-cache.js +116 -0
  80. package/dist/utils/performance.js +199 -0
  81. package/dist/utils/retry.js +240 -0
  82. package/docs/INTEGRATION_PLAN.md +475 -0
  83. package/docs/INTEGRATION_SUMMARY.md +215 -0
  84. package/docs/MELHORIAS_IMPLEMENTADAS.md +228 -0
  85. package/docs/MELHORIAS_PROFISSIONAIS.md +282 -0
  86. package/docs/PLANO_ACAO_TUI.md +357 -0
  87. package/docs/PROGRESSO_TUI.md +66 -0
  88. package/docs/RELATORIO_FINAL.md +217 -0
  89. package/docs/diagnostico-shell-completion.md +265 -0
  90. package/docs/features/advanced-memory.md +585 -0
  91. package/docs/features/discord-components-v2.md +277 -0
  92. package/docs/features/swarm.md +100 -0
  93. package/docs/features/telemetry.md +284 -0
  94. package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
  95. package/docs/integrations/INTEGRATION_PLAN.md +744 -0
  96. package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
  97. package/docs/integrations/XYOPS_PLAN.md +978 -0
  98. package/docs/models/provider-infrastructure.md +400 -0
  99. package/docs/security/exec-approvals.md +294 -0
  100. package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
  101. package/docs/skills/SKILL.md +524 -0
  102. package/docs/skills.md +405 -0
  103. package/extensions/bluebubbles/package.json +1 -1
  104. package/extensions/copilot-proxy/package.json +1 -1
  105. package/extensions/diagnostics-otel/package.json +1 -1
  106. package/extensions/discord/package.json +1 -1
  107. package/extensions/feishu/package.json +1 -1
  108. package/extensions/google-antigravity-auth/package.json +1 -1
  109. package/extensions/google-gemini-cli-auth/package.json +1 -1
  110. package/extensions/googlechat/package.json +1 -1
  111. package/extensions/hexstrike-bridge/README.md +119 -0
  112. package/extensions/hexstrike-bridge/index.test.ts +247 -0
  113. package/extensions/hexstrike-bridge/index.ts +487 -0
  114. package/extensions/hexstrike-bridge/package.json +17 -0
  115. package/extensions/imessage/package.json +1 -1
  116. package/extensions/irc/package.json +1 -1
  117. package/extensions/line/package.json +1 -1
  118. package/extensions/llm-task/package.json +1 -1
  119. package/extensions/lobster/package.json +1 -1
  120. package/extensions/matrix/CHANGELOG.md +5 -0
  121. package/extensions/matrix/package.json +1 -1
  122. package/extensions/mattermost/package.json +1 -1
  123. package/extensions/mcp-server/index.ts +14 -0
  124. package/extensions/mcp-server/package.json +11 -0
  125. package/extensions/mcp-server/src/service.ts +540 -0
  126. package/extensions/memory-core/package.json +1 -1
  127. package/extensions/memory-lancedb/package.json +1 -1
  128. package/extensions/minimax-portal-auth/package.json +1 -1
  129. package/extensions/msteams/CHANGELOG.md +5 -0
  130. package/extensions/msteams/package.json +1 -1
  131. package/extensions/nextcloud-talk/package.json +1 -1
  132. package/extensions/nostr/CHANGELOG.md +5 -0
  133. package/extensions/nostr/package.json +1 -1
  134. package/extensions/open-prose/package.json +1 -1
  135. package/extensions/openai-codex-auth/package.json +1 -1
  136. package/extensions/signal/package.json +1 -1
  137. package/extensions/slack/package.json +1 -1
  138. package/extensions/telegram/package.json +1 -1
  139. package/extensions/tlon/package.json +1 -1
  140. package/extensions/twitch/CHANGELOG.md +5 -0
  141. package/extensions/twitch/package.json +1 -1
  142. package/extensions/voice-call/CHANGELOG.md +5 -0
  143. package/extensions/voice-call/package.json +1 -1
  144. package/extensions/whatsapp/package.json +1 -1
  145. package/extensions/zalo/CHANGELOG.md +5 -0
  146. package/extensions/zalo/package.json +1 -1
  147. package/extensions/zalouser/CHANGELOG.md +5 -0
  148. package/extensions/zalouser/package.json +1 -1
  149. package/package.json +8 -1
  150. package/skills/example-skill/SKILL.md +195 -0
@@ -1,8 +1,18 @@
1
1
  import { getEditorKeybindings, Input, isKeyRelease, matchesKey, truncateToWidth, } from "@mariozechner/pi-tui";
2
+ import { LRUCache } from "../../utils/lru-cache.js";
2
3
  import { visibleWidth } from "../../terminal/ansi.js";
3
4
  import { findWordBoundaryIndex, fuzzyFilterLower, prepareSearchItems } from "./fuzzy-filter.js";
5
+ /**
6
+ * Maximum number of cached regex patterns to prevent memory leaks
7
+ */
8
+ const MAX_REGEX_CACHE_SIZE = 100;
4
9
  /**
5
10
  * A select list with a search input at the top for fuzzy filtering.
11
+ *
12
+ * Features:
13
+ * - Smart tiered filtering (exact > word-boundary > description > fuzzy)
14
+ * - LRU-cached regex patterns for performance
15
+ * - Keyboard navigation with vim-style bindings
6
16
  */
7
17
  export class SearchableSelectList {
8
18
  items;
@@ -11,7 +21,7 @@ export class SearchableSelectList {
11
21
  maxVisible;
12
22
  theme;
13
23
  searchInput;
14
- regexCache = new Map();
24
+ regexCache;
15
25
  onSelect;
16
26
  onCancel;
17
27
  onSelectionChange;
@@ -21,6 +31,7 @@ export class SearchableSelectList {
21
31
  this.maxVisible = maxVisible;
22
32
  this.theme = theme;
23
33
  this.searchInput = new Input();
34
+ this.regexCache = new LRUCache({ maxSize: MAX_REGEX_CACHE_SIZE });
24
35
  }
25
36
  getCachedRegex(pattern) {
26
37
  let regex = this.regexCache.get(pattern);
@@ -1,10 +1,13 @@
1
1
  import { Container, Markdown, Spacer } from "@mariozechner/pi-tui";
2
2
  import { markdownTheme, theme } from "../theme/theme.js";
3
+ import { sanitizeRenderableText } from "../utils/text-sanitization.js";
3
4
  export class UserMessageComponent extends Container {
4
5
  body;
5
6
  constructor(text) {
6
7
  super();
7
- this.body = new Markdown(text, 1, 1, markdownTheme, {
8
+ // Sanitize text before rendering to prevent terminal corruption
9
+ const sanitizedText = sanitizeRenderableText(text);
10
+ this.body = new Markdown(sanitizedText, 1, 1, markdownTheme, {
8
11
  bgColor: (line) => theme.userBg(line),
9
12
  color: (line) => theme.userText(line),
10
13
  });
@@ -12,6 +15,7 @@ export class UserMessageComponent extends Container {
12
15
  this.addChild(this.body);
13
16
  }
14
17
  setText(text) {
15
- this.body.setText(text);
18
+ // Sanitize text before updating
19
+ this.body.setText(sanitizeRenderableText(text));
16
20
  }
17
21
  }
@@ -0,0 +1,611 @@
1
+ /**
2
+ * Terminal UI (TUI) Components
3
+ *
4
+ * Modern, interactive terminal interface components for PoolBot.
5
+ * Provides rich terminal experiences with progress bars, spinners,
6
+ * tables, and interactive prompts.
7
+ */
8
+ import { stdin, stdout } from "node:process";
9
+ import { createInterface } from "node:readline";
10
+ /**
11
+ * Detect terminal capabilities
12
+ */
13
+ export function detectCapabilities() {
14
+ const isTTY = stdout.isTTY ?? false;
15
+ const env = process.env;
16
+ // Check color support
17
+ const term = env.TERM ?? "";
18
+ const colorterm = env.COLORTERM ?? "";
19
+ const trueColor = colorterm === "truecolor" || colorterm === "24bit";
20
+ const colors256 = trueColor || term.includes("256") || term.includes("color");
21
+ // Check Unicode support
22
+ const lang = env.LANG ?? "";
23
+ const lcAll = env.LC_ALL ?? "";
24
+ const unicode = /utf-?8/i.test(lang) || /utf-?8/i.test(lcAll) || process.platform !== "win32";
25
+ return {
26
+ unicode,
27
+ colors256,
28
+ trueColor,
29
+ width: stdout.columns ?? 80,
30
+ height: stdout.rows ?? 24,
31
+ cursor: isTTY,
32
+ isTTY,
33
+ };
34
+ }
35
+ /**
36
+ * ANSI color codes
37
+ */
38
+ export const Colors = {
39
+ // Reset
40
+ reset: "\x1b[0m",
41
+ // Foreground colors
42
+ black: "\x1b[30m",
43
+ red: "\x1b[31m",
44
+ green: "\x1b[32m",
45
+ yellow: "\x1b[33m",
46
+ blue: "\x1b[34m",
47
+ magenta: "\x1b[35m",
48
+ cyan: "\x1b[36m",
49
+ white: "\x1b[37m",
50
+ gray: "\x1b[90m",
51
+ // Bright colors
52
+ brightRed: "\x1b[91m",
53
+ brightGreen: "\x1b[92m",
54
+ brightYellow: "\x1b[93m",
55
+ brightBlue: "\x1b[94m",
56
+ brightMagenta: "\x1b[95m",
57
+ brightCyan: "\x1b[96m",
58
+ brightWhite: "\x1b[97m",
59
+ // Background colors
60
+ bgBlack: "\x1b[40m",
61
+ bgRed: "\x1b[41m",
62
+ bgGreen: "\x1b[42m",
63
+ bgYellow: "\x1b[43m",
64
+ bgBlue: "\x1b[44m",
65
+ bgMagenta: "\x1b[45m",
66
+ bgCyan: "\x1b[46m",
67
+ bgWhite: "\x1b[47m",
68
+ // Styles
69
+ bold: "\x1b[1m",
70
+ dim: "\x1b[2m",
71
+ italic: "\x1b[3m",
72
+ underline: "\x1b[4m",
73
+ strikethrough: "\x1b[9m",
74
+ };
75
+ /**
76
+ * Colorize text with ANSI codes
77
+ */
78
+ export function colorize(text, ...codes) {
79
+ const caps = detectCapabilities();
80
+ if (!caps.colors256)
81
+ return text;
82
+ return codes.join("") + text + Colors.reset;
83
+ }
84
+ /**
85
+ * Spinner characters for different styles
86
+ */
87
+ export const SpinnerChars = {
88
+ dots: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
89
+ line: ["-", "\\", "|", "/"],
90
+ arrow: ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
91
+ bounce: [
92
+ "( ● )",
93
+ "( ● )",
94
+ "( ● )",
95
+ "( ● )",
96
+ "( ●)",
97
+ "( ● )",
98
+ "( ● )",
99
+ "( ● )",
100
+ "( ● )",
101
+ "(● )",
102
+ ],
103
+ pulse: ["█", "▉", "▊", "▋", "▌", "▍", "▎", "▏"],
104
+ };
105
+ /**
106
+ * Terminal spinner for loading states
107
+ */
108
+ export class Spinner {
109
+ frames;
110
+ interval;
111
+ text;
112
+ color;
113
+ currentFrame = 0;
114
+ timer = null;
115
+ active = false;
116
+ caps;
117
+ constructor(options) {
118
+ this.caps = detectCapabilities();
119
+ this.frames = SpinnerChars[options.style ?? "dots"];
120
+ this.interval = options.interval ?? 80;
121
+ this.text = options.text;
122
+ this.color = options.color ?? Colors.cyan;
123
+ }
124
+ /**
125
+ * Start the spinner
126
+ */
127
+ start() {
128
+ if (this.active || !this.caps.isTTY)
129
+ return this;
130
+ this.active = true;
131
+ this.render();
132
+ this.timer = setInterval(() => {
133
+ this.currentFrame = (this.currentFrame + 1) % this.frames.length;
134
+ this.render();
135
+ }, this.interval);
136
+ return this;
137
+ }
138
+ /**
139
+ * Stop the spinner
140
+ */
141
+ stop(finalText) {
142
+ if (!this.active)
143
+ return;
144
+ if (this.timer) {
145
+ clearInterval(this.timer);
146
+ this.timer = null;
147
+ }
148
+ this.active = false;
149
+ // Clear line and show final text
150
+ if (finalText) {
151
+ stdout.write("\r\x1b[K" + finalText + "\n");
152
+ }
153
+ else {
154
+ stdout.write("\r\x1b[K");
155
+ }
156
+ }
157
+ /**
158
+ * Update spinner text
159
+ */
160
+ update(text) {
161
+ this.text = text;
162
+ if (this.active) {
163
+ this.render();
164
+ }
165
+ }
166
+ /**
167
+ * Render current frame
168
+ */
169
+ render() {
170
+ if (!this.caps.isTTY)
171
+ return;
172
+ const frame = this.frames[this.currentFrame];
173
+ const coloredFrame = this.caps.colors256 ? this.color + frame + Colors.reset : frame;
174
+ stdout.write(`\r\x1b[K${coloredFrame} ${this.text}`);
175
+ }
176
+ }
177
+ /**
178
+ * Terminal progress bar
179
+ */
180
+ export class ProgressBar {
181
+ total;
182
+ current = 0;
183
+ width;
184
+ completeChar;
185
+ incompleteChar;
186
+ showPercent;
187
+ showValue;
188
+ prefix;
189
+ completeColor;
190
+ incompleteColor;
191
+ caps;
192
+ constructor(options) {
193
+ this.caps = detectCapabilities();
194
+ this.total = options.total;
195
+ this.width = options.width ?? 40;
196
+ this.completeChar = options.completeChar ?? (this.caps.unicode ? "█" : "=");
197
+ this.incompleteChar = options.incompleteChar ?? (this.caps.unicode ? "░" : "-");
198
+ this.showPercent = options.showPercent ?? true;
199
+ this.showValue = options.showValue ?? false;
200
+ this.prefix = options.prefix ?? "";
201
+ this.completeColor = options.completeColor ?? Colors.green;
202
+ this.incompleteColor = options.incompleteColor ?? Colors.gray;
203
+ }
204
+ /**
205
+ * Update progress
206
+ */
207
+ update(value) {
208
+ this.current = Math.min(value, this.total);
209
+ this.render();
210
+ }
211
+ /**
212
+ * Increment progress
213
+ */
214
+ increment(amount = 1) {
215
+ this.update(this.current + amount);
216
+ }
217
+ /**
218
+ * Complete the progress bar
219
+ */
220
+ complete(message) {
221
+ this.update(this.total);
222
+ if (message) {
223
+ stdout.write(" " + message);
224
+ }
225
+ stdout.write("\n");
226
+ }
227
+ /**
228
+ * Render the progress bar
229
+ */
230
+ render() {
231
+ if (!this.caps.isTTY)
232
+ return;
233
+ const percent = this.current / this.total;
234
+ const filled = Math.round(this.width * percent);
235
+ const empty = this.width - filled;
236
+ const completeStr = this.completeChar.repeat(filled);
237
+ const incompleteStr = this.incompleteChar.repeat(empty);
238
+ const coloredComplete = this.caps.colors256
239
+ ? this.completeColor + completeStr + Colors.reset
240
+ : completeStr;
241
+ const coloredIncomplete = this.caps.colors256
242
+ ? this.incompleteColor + incompleteStr + Colors.reset
243
+ : incompleteStr;
244
+ let suffix = "";
245
+ if (this.showPercent) {
246
+ suffix += ` ${(percent * 100).toFixed(1)}%`;
247
+ }
248
+ if (this.showValue) {
249
+ suffix += ` [${this.current}/${this.total}]`;
250
+ }
251
+ const prefix = this.prefix ? this.prefix + " " : "";
252
+ stdout.write(`\r\x1b[K${prefix}${coloredComplete}${coloredIncomplete}${suffix}`);
253
+ }
254
+ }
255
+ /**
256
+ * Border characters for different styles
257
+ */
258
+ const BorderChars = {
259
+ simple: {
260
+ topLeft: "+",
261
+ topRight: "+",
262
+ bottomLeft: "+",
263
+ bottomRight: "+",
264
+ horizontal: "-",
265
+ vertical: "|",
266
+ cross: "+",
267
+ leftT: "+",
268
+ rightT: "+",
269
+ topT: "+",
270
+ bottomT: "+",
271
+ },
272
+ rounded: {
273
+ topLeft: "╭",
274
+ topRight: "╮",
275
+ bottomLeft: "╰",
276
+ bottomRight: "╯",
277
+ horizontal: "─",
278
+ vertical: "│",
279
+ cross: "┼",
280
+ leftT: "├",
281
+ rightT: "┤",
282
+ topT: "┬",
283
+ bottomT: "┴",
284
+ },
285
+ heavy: {
286
+ topLeft: "┏",
287
+ topRight: "┓",
288
+ bottomLeft: "┗",
289
+ bottomRight: "┛",
290
+ horizontal: "━",
291
+ vertical: "┃",
292
+ cross: "╋",
293
+ leftT: "┣",
294
+ rightT: "┫",
295
+ topT: "┳",
296
+ bottomT: "┻",
297
+ },
298
+ none: {
299
+ topLeft: "",
300
+ topRight: "",
301
+ bottomLeft: "",
302
+ bottomRight: "",
303
+ horizontal: "",
304
+ vertical: "",
305
+ cross: "",
306
+ leftT: "",
307
+ rightT: "",
308
+ topT: "",
309
+ bottomT: "",
310
+ },
311
+ };
312
+ /**
313
+ * Terminal table renderer
314
+ */
315
+ export class Table {
316
+ columns;
317
+ data;
318
+ showHeader;
319
+ borderStyle;
320
+ rowSeparator;
321
+ caps;
322
+ constructor(options) {
323
+ this.caps = detectCapabilities();
324
+ this.columns = options.columns;
325
+ this.data = options.data;
326
+ this.showHeader = options.showHeader ?? true;
327
+ this.borderStyle = options.borderStyle ?? "simple";
328
+ this.rowSeparator = options.rowSeparator ?? false;
329
+ }
330
+ /**
331
+ * Render the table
332
+ */
333
+ render() {
334
+ const borders = BorderChars[this.borderStyle];
335
+ const colWidths = this.calculateWidths();
336
+ const lines = [];
337
+ // Top border
338
+ if (this.borderStyle !== "none") {
339
+ lines.push(this.renderHorizontalLine(borders.topLeft, borders.topRight, borders.horizontal, borders.topT, colWidths));
340
+ }
341
+ // Header row
342
+ if (this.showHeader) {
343
+ const headerCells = this.columns.map((col, i) => {
344
+ const width = colWidths[i];
345
+ const text = this.truncate(col.header, width);
346
+ const aligned = this.align(text, width, col.align ?? "left");
347
+ const colored = col.headerColor && this.caps.colors256
348
+ ? col.headerColor + aligned + Colors.reset
349
+ : aligned;
350
+ return colored;
351
+ });
352
+ lines.push(this.renderRow(headerCells, borders.vertical));
353
+ // Header separator
354
+ if (this.borderStyle !== "none") {
355
+ lines.push(this.renderHorizontalLine(borders.leftT, borders.rightT, borders.horizontal, borders.cross, colWidths));
356
+ }
357
+ }
358
+ // Data rows
359
+ for (let i = 0; i < this.data.length; i++) {
360
+ const row = this.data[i];
361
+ const cells = row.map((cell, j) => {
362
+ const width = colWidths[j];
363
+ const text = this.truncate(cell ?? "", width);
364
+ return this.align(text, width, this.columns[j]?.align ?? "left");
365
+ });
366
+ lines.push(this.renderRow(cells, borders.vertical));
367
+ // Row separator
368
+ if (this.rowSeparator && i < this.data.length - 1 && this.borderStyle !== "none") {
369
+ lines.push(this.renderHorizontalLine(borders.leftT, borders.rightT, borders.horizontal, borders.cross, colWidths));
370
+ }
371
+ }
372
+ // Bottom border
373
+ if (this.borderStyle !== "none") {
374
+ lines.push(this.renderHorizontalLine(borders.bottomLeft, borders.bottomRight, borders.horizontal, borders.bottomT, colWidths));
375
+ }
376
+ return lines.join("\n");
377
+ }
378
+ /**
379
+ * Print the table to stdout
380
+ */
381
+ print() {
382
+ console.log(this.render());
383
+ }
384
+ calculateWidths() {
385
+ const caps = detectCapabilities();
386
+ const maxWidth = caps.width - 4; // Padding for borders
387
+ return this.columns.map((col, i) => {
388
+ if (col.width)
389
+ return col.width;
390
+ // Calculate based on content
391
+ const headerLen = col.header.length;
392
+ const maxDataLen = this.data.reduce((max, row) => {
393
+ const cellLen = (row[i] ?? "").length;
394
+ return Math.max(max, cellLen);
395
+ }, 0);
396
+ return Math.min(Math.max(headerLen, maxDataLen) + 2, Math.floor(maxWidth / this.columns.length));
397
+ });
398
+ }
399
+ renderHorizontalLine(left, right, horizontal, cross, widths) {
400
+ const segments = widths.map((w) => horizontal.repeat(w));
401
+ return left + segments.join(cross) + right;
402
+ }
403
+ renderRow(cells, vertical) {
404
+ return vertical + cells.join(vertical) + vertical;
405
+ }
406
+ truncate(text, width) {
407
+ if (text.length <= width)
408
+ return text;
409
+ return text.slice(0, width - 3) + "...";
410
+ }
411
+ align(text, width, align) {
412
+ const padding = width - text.length;
413
+ switch (align) {
414
+ case "center": {
415
+ const left = Math.floor(padding / 2);
416
+ const right = padding - left;
417
+ return " ".repeat(left) + text + " ".repeat(right);
418
+ }
419
+ case "right":
420
+ return " ".repeat(padding) + text;
421
+ case "left":
422
+ default:
423
+ return text + " ".repeat(padding);
424
+ }
425
+ }
426
+ }
427
+ /**
428
+ * Interactive prompts
429
+ */
430
+ export class Prompts {
431
+ /**
432
+ * Confirm yes/no prompt
433
+ */
434
+ static async confirm(options) {
435
+ const defaultText = options.default === false ? "y/N" : "Y/n";
436
+ const rl = createInterface({ input: stdin, output: stdout });
437
+ try {
438
+ const answer = await new Promise((resolve) => {
439
+ rl.question(`${options.message} [${defaultText}] `, resolve);
440
+ });
441
+ const normalized = answer.trim().toLowerCase();
442
+ if (normalized === "")
443
+ return options.default ?? true;
444
+ return normalized === "y" || normalized === "yes";
445
+ }
446
+ finally {
447
+ rl.close();
448
+ }
449
+ }
450
+ /**
451
+ * Text input prompt
452
+ */
453
+ static async input(options) {
454
+ const rl = createInterface({ input: stdin, output: stdout });
455
+ try {
456
+ const prompt = options.default
457
+ ? `${options.message} (${options.default}) `
458
+ : `${options.message} `;
459
+ const answer = await new Promise((resolve) => {
460
+ rl.question(prompt, resolve);
461
+ });
462
+ const value = answer.trim() || options.default || "";
463
+ if (options.validate) {
464
+ const result = options.validate(value);
465
+ if (result !== true) {
466
+ console.log(colorize(`Error: ${result}`, Colors.red));
467
+ return Prompts.input(options);
468
+ }
469
+ }
470
+ return value;
471
+ }
472
+ finally {
473
+ rl.close();
474
+ }
475
+ }
476
+ /**
477
+ * Select from list prompt
478
+ */
479
+ static async select(options) {
480
+ const available = options.choices.filter((c) => !c.disabled);
481
+ console.log(options.message);
482
+ available.forEach((choice, i) => {
483
+ const marker = choice.value === options.default ? colorize("›", Colors.cyan) : " ";
484
+ console.log(` ${marker} ${i + 1}) ${choice.name}`);
485
+ });
486
+ const rl = createInterface({ input: stdin, output: stdout });
487
+ try {
488
+ const answer = await new Promise((resolve) => {
489
+ rl.question("Select: ", resolve);
490
+ });
491
+ const num = parseInt(answer.trim(), 10);
492
+ if (num >= 1 && num <= available.length) {
493
+ return available[num - 1].value;
494
+ }
495
+ // Try matching by value
496
+ const match = available.find((c) => c.value.toLowerCase() === answer.trim().toLowerCase());
497
+ if (match)
498
+ return match.value;
499
+ console.log(colorize("Invalid selection", Colors.red));
500
+ return Prompts.select(options);
501
+ }
502
+ finally {
503
+ rl.close();
504
+ }
505
+ }
506
+ }
507
+ /**
508
+ * Status indicators
509
+ */
510
+ export const Status = {
511
+ success: colorize("✓", Colors.green),
512
+ error: colorize("✗", Colors.red),
513
+ warning: colorize("⚠", Colors.yellow),
514
+ info: colorize("ℹ", Colors.blue),
515
+ pending: colorize("○", Colors.gray),
516
+ };
517
+ /**
518
+ * Log a message with a status indicator
519
+ */
520
+ export function logStatus(status, message) {
521
+ console.log(`${Status[status]} ${message}`);
522
+ }
523
+ /**
524
+ * Draw a box around content
525
+ */
526
+ export function box(content, options = {}) {
527
+ const caps = detectCapabilities();
528
+ const lines = content.split("\n");
529
+ const maxLineLen = Math.max(...lines.map((l) => l.length));
530
+ const width = options.width ?? maxLineLen + 4;
531
+ const padding = options.padding ?? 1;
532
+ const borders = {
533
+ simple: { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" },
534
+ rounded: { tl: "╭", tr: "╮", bl: "╰", br: "╯", h: "─", v: "│" },
535
+ heavy: { tl: "┏", tr: "┓", bl: "┗", br: "┛", h: "━", v: "┃" },
536
+ double: { tl: "╔", tr: "╗", bl: "╚", br: "╝", h: "═", v: "║" },
537
+ }[options.style ?? "rounded"];
538
+ const result = [];
539
+ // Top border with title
540
+ let topBorder = borders.tl + borders.h.repeat(width - 2) + borders.tr;
541
+ if (options.title) {
542
+ const title = caps.colors256 && options.titleColor
543
+ ? options.titleColor + options.title + Colors.reset
544
+ : options.title;
545
+ const titleWithPadding = ` ${title} `;
546
+ const before = Math.floor((width - titleWithPadding.length) / 2);
547
+ topBorder =
548
+ borders.tl +
549
+ borders.h.repeat(before) +
550
+ titleWithPadding +
551
+ borders.h.repeat(width - before - titleWithPadding.length - 2) +
552
+ borders.tr;
553
+ }
554
+ result.push(topBorder);
555
+ // Padding top
556
+ for (let i = 0; i < padding; i++) {
557
+ result.push(borders.v + " ".repeat(width - 2) + borders.v);
558
+ }
559
+ // Content
560
+ for (const line of lines) {
561
+ const contentWidth = Math.max(0, width - line.length - 2 - padding * 2);
562
+ const padded = " ".repeat(padding) + line + " ".repeat(contentWidth);
563
+ result.push(borders.v + padded + borders.v);
564
+ }
565
+ // Padding bottom
566
+ for (let i = 0; i < padding; i++) {
567
+ result.push(borders.v + " ".repeat(width - 2) + borders.v);
568
+ }
569
+ // Bottom border
570
+ result.push(borders.bl + borders.h.repeat(width - 2) + borders.br);
571
+ return result.join("\n");
572
+ }
573
+ /**
574
+ * Create a multi-step wizard
575
+ */
576
+ export class Wizard {
577
+ steps = [];
578
+ _currentStep = 0;
579
+ /**
580
+ * Add a step to the wizard
581
+ */
582
+ step(name, run) {
583
+ this.steps.push({ name, run });
584
+ return this;
585
+ }
586
+ /**
587
+ * Run the wizard
588
+ */
589
+ async run() {
590
+ console.log(box("Starting Setup Wizard", { title: "Wizard", style: "rounded" }));
591
+ for (let i = 0; i < this.steps.length; i++) {
592
+ this._currentStep = i;
593
+ const step = this.steps[i];
594
+ console.log(`\n${colorize(`Step ${i + 1}/${this.steps.length}:`, Colors.cyan)} ${step.name}`);
595
+ try {
596
+ const success = await step.run();
597
+ if (!success) {
598
+ logStatus("error", `Step "${step.name}" failed`);
599
+ return false;
600
+ }
601
+ logStatus("success", `Step "${step.name}" completed`);
602
+ }
603
+ catch (error) {
604
+ logStatus("error", `Step "${step.name}" error: ${error instanceof Error ? error.message : String(error)}`);
605
+ return false;
606
+ }
607
+ }
608
+ console.log("\n" + box("Wizard completed successfully!", { style: "rounded", title: "✓ Complete" }));
609
+ return true;
610
+ }
611
+ }