@poolzin/pool-bot 2026.3.6 → 2026.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +302 -0
- package/dist/agents/pi-tools.js +32 -2
- package/dist/agents/skills/security.js +217 -0
- package/dist/auto-reply/reply/get-reply.js +6 -0
- package/dist/auto-reply/reply/message-preprocess-hooks.js +17 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/banner.js +20 -1
- package/dist/cli/lazy-commands.example.js +113 -0
- package/dist/cli/lazy-commands.js +329 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.skills.js +4 -0
- package/dist/cli/security-cli.js +211 -2
- package/dist/cli/tagline.js +7 -0
- package/dist/config/config.js +1 -0
- package/dist/config/secrets-integration.js +88 -0
- package/dist/config/types.cli.js +1 -0
- package/dist/config/types.security.js +33 -0
- package/dist/config/zod-schema.js +15 -0
- package/dist/config/zod-schema.providers-core.js +1 -0
- package/dist/config/zod-schema.security.js +113 -0
- package/dist/context-engine/index.js +33 -0
- package/dist/context-engine/legacy.js +181 -0
- package/dist/context-engine/registry.js +86 -0
- package/dist/context-engine/summarizing.js +293 -0
- package/dist/context-engine/types.js +7 -0
- package/dist/discord/monitor/message-handler.preflight.js +11 -2
- package/dist/gateway/http-common.js +6 -1
- package/dist/hooks/fire-and-forget.js +6 -0
- package/dist/hooks/internal-hooks.js +64 -19
- package/dist/hooks/message-hook-mappers.js +179 -0
- package/dist/infra/abort-pattern.js +106 -0
- package/dist/infra/retry.js +94 -0
- package/dist/secrets/index.js +28 -0
- package/dist/secrets/resolver.js +185 -0
- package/dist/secrets/runtime.js +142 -0
- package/dist/secrets/types.js +11 -0
- package/dist/security/capability-guards.js +89 -0
- package/dist/security/capability-manager.js +76 -0
- package/dist/security/capability.js +147 -0
- package/dist/security/dangerous-tools.js +80 -0
- package/dist/security/index.js +7 -0
- package/dist/security/middleware.js +105 -0
- package/dist/security/types.js +12 -0
- package/dist/skills/commands.js +351 -0
- package/dist/skills/index.js +167 -0
- package/dist/skills/loader.js +282 -0
- package/dist/skills/parser.js +461 -0
- package/dist/skills/registry.js +397 -0
- package/dist/skills/security.js +318 -0
- package/dist/skills/types.js +21 -0
- package/dist/slack/monitor/context.js +1 -0
- package/dist/slack/monitor/message-handler/dispatch.js +14 -1
- package/dist/slack/monitor/provider.js +2 -0
- package/dist/test-utils/index.js +219 -0
- package/dist/tui/index.js +595 -0
- package/docs/INTEGRATION_PLAN.md +475 -0
- package/docs/INTEGRATION_SUMMARY.md +215 -0
- package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
- package/docs/integrations/INTEGRATION_PLAN.md +424 -0
- package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
- package/docs/integrations/XYOPS_PLAN.md +978 -0
- package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
- package/docs/skills/SKILL.md +524 -0
- package/docs/skills.md +405 -0
- package/package.json +1 -1
- package/skills/example-skill/SKILL.md +195 -0
|
@@ -0,0 +1,595 @@
|
|
|
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
|
+
pulse: ["█", "▉", "▊", "▋", "▌", "▍", "▎", "▏"],
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Terminal spinner for loading states
|
|
96
|
+
*/
|
|
97
|
+
export class Spinner {
|
|
98
|
+
frames;
|
|
99
|
+
interval;
|
|
100
|
+
text;
|
|
101
|
+
color;
|
|
102
|
+
currentFrame = 0;
|
|
103
|
+
timer = null;
|
|
104
|
+
active = false;
|
|
105
|
+
caps;
|
|
106
|
+
constructor(options) {
|
|
107
|
+
this.caps = detectCapabilities();
|
|
108
|
+
this.frames = SpinnerChars[options.style ?? "dots"];
|
|
109
|
+
this.interval = options.interval ?? 80;
|
|
110
|
+
this.text = options.text;
|
|
111
|
+
this.color = options.color ?? Colors.cyan;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Start the spinner
|
|
115
|
+
*/
|
|
116
|
+
start() {
|
|
117
|
+
if (this.active || !this.caps.isTTY)
|
|
118
|
+
return this;
|
|
119
|
+
this.active = true;
|
|
120
|
+
this.render();
|
|
121
|
+
this.timer = setInterval(() => {
|
|
122
|
+
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
123
|
+
this.render();
|
|
124
|
+
}, this.interval);
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Stop the spinner
|
|
129
|
+
*/
|
|
130
|
+
stop(finalText) {
|
|
131
|
+
if (!this.active)
|
|
132
|
+
return;
|
|
133
|
+
if (this.timer) {
|
|
134
|
+
clearInterval(this.timer);
|
|
135
|
+
this.timer = null;
|
|
136
|
+
}
|
|
137
|
+
this.active = false;
|
|
138
|
+
// Clear line and show final text
|
|
139
|
+
if (finalText) {
|
|
140
|
+
stdout.write("\r\x1b[K" + finalText + "\n");
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
stdout.write("\r\x1b[K");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Update spinner text
|
|
148
|
+
*/
|
|
149
|
+
update(text) {
|
|
150
|
+
this.text = text;
|
|
151
|
+
if (this.active) {
|
|
152
|
+
this.render();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Render current frame
|
|
157
|
+
*/
|
|
158
|
+
render() {
|
|
159
|
+
if (!this.caps.isTTY)
|
|
160
|
+
return;
|
|
161
|
+
const frame = this.frames[this.currentFrame];
|
|
162
|
+
const coloredFrame = this.caps.colors256 ? this.color + frame + Colors.reset : frame;
|
|
163
|
+
stdout.write(`\r\x1b[K${coloredFrame} ${this.text}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Terminal progress bar
|
|
168
|
+
*/
|
|
169
|
+
export class ProgressBar {
|
|
170
|
+
total;
|
|
171
|
+
current = 0;
|
|
172
|
+
width;
|
|
173
|
+
completeChar;
|
|
174
|
+
incompleteChar;
|
|
175
|
+
showPercent;
|
|
176
|
+
showValue;
|
|
177
|
+
prefix;
|
|
178
|
+
completeColor;
|
|
179
|
+
incompleteColor;
|
|
180
|
+
caps;
|
|
181
|
+
constructor(options) {
|
|
182
|
+
this.caps = detectCapabilities();
|
|
183
|
+
this.total = options.total;
|
|
184
|
+
this.width = options.width ?? 40;
|
|
185
|
+
this.completeChar = options.completeChar ?? (this.caps.unicode ? "█" : "=");
|
|
186
|
+
this.incompleteChar = options.incompleteChar ?? (this.caps.unicode ? "░" : "-");
|
|
187
|
+
this.showPercent = options.showPercent ?? true;
|
|
188
|
+
this.showValue = options.showValue ?? false;
|
|
189
|
+
this.prefix = options.prefix ?? "";
|
|
190
|
+
this.completeColor = options.completeColor ?? Colors.green;
|
|
191
|
+
this.incompleteColor = options.incompleteColor ?? Colors.gray;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Update progress
|
|
195
|
+
*/
|
|
196
|
+
update(value) {
|
|
197
|
+
this.current = Math.min(value, this.total);
|
|
198
|
+
this.render();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Increment progress
|
|
202
|
+
*/
|
|
203
|
+
increment(amount = 1) {
|
|
204
|
+
this.update(this.current + amount);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Complete the progress bar
|
|
208
|
+
*/
|
|
209
|
+
complete(message) {
|
|
210
|
+
this.update(this.total);
|
|
211
|
+
if (message) {
|
|
212
|
+
stdout.write(" " + message);
|
|
213
|
+
}
|
|
214
|
+
stdout.write("\n");
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Render the progress bar
|
|
218
|
+
*/
|
|
219
|
+
render() {
|
|
220
|
+
if (!this.caps.isTTY)
|
|
221
|
+
return;
|
|
222
|
+
const percent = this.current / this.total;
|
|
223
|
+
const filled = Math.round(this.width * percent);
|
|
224
|
+
const empty = this.width - filled;
|
|
225
|
+
const completeStr = this.completeChar.repeat(filled);
|
|
226
|
+
const incompleteStr = this.incompleteChar.repeat(empty);
|
|
227
|
+
const coloredComplete = this.caps.colors256
|
|
228
|
+
? this.completeColor + completeStr + Colors.reset
|
|
229
|
+
: completeStr;
|
|
230
|
+
const coloredIncomplete = this.caps.colors256
|
|
231
|
+
? this.incompleteColor + incompleteStr + Colors.reset
|
|
232
|
+
: incompleteStr;
|
|
233
|
+
let suffix = "";
|
|
234
|
+
if (this.showPercent) {
|
|
235
|
+
suffix += ` ${(percent * 100).toFixed(1)}%`;
|
|
236
|
+
}
|
|
237
|
+
if (this.showValue) {
|
|
238
|
+
suffix += ` [${this.current}/${this.total}]`;
|
|
239
|
+
}
|
|
240
|
+
const prefix = this.prefix ? this.prefix + " " : "";
|
|
241
|
+
stdout.write(`\r\x1b[K${prefix}${coloredComplete}${coloredIncomplete}${suffix}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Border characters for different styles
|
|
246
|
+
*/
|
|
247
|
+
const BorderChars = {
|
|
248
|
+
simple: {
|
|
249
|
+
topLeft: "+",
|
|
250
|
+
topRight: "+",
|
|
251
|
+
bottomLeft: "+",
|
|
252
|
+
bottomRight: "+",
|
|
253
|
+
horizontal: "-",
|
|
254
|
+
vertical: "|",
|
|
255
|
+
cross: "+",
|
|
256
|
+
leftT: "+",
|
|
257
|
+
rightT: "+",
|
|
258
|
+
topT: "+",
|
|
259
|
+
bottomT: "+",
|
|
260
|
+
},
|
|
261
|
+
rounded: {
|
|
262
|
+
topLeft: "╭",
|
|
263
|
+
topRight: "╮",
|
|
264
|
+
bottomLeft: "╰",
|
|
265
|
+
bottomRight: "╯",
|
|
266
|
+
horizontal: "─",
|
|
267
|
+
vertical: "│",
|
|
268
|
+
cross: "┼",
|
|
269
|
+
leftT: "├",
|
|
270
|
+
rightT: "┤",
|
|
271
|
+
topT: "┬",
|
|
272
|
+
bottomT: "┴",
|
|
273
|
+
},
|
|
274
|
+
heavy: {
|
|
275
|
+
topLeft: "┏",
|
|
276
|
+
topRight: "┓",
|
|
277
|
+
bottomLeft: "┗",
|
|
278
|
+
bottomRight: "┛",
|
|
279
|
+
horizontal: "━",
|
|
280
|
+
vertical: "┃",
|
|
281
|
+
cross: "╋",
|
|
282
|
+
leftT: "┣",
|
|
283
|
+
rightT: "┫",
|
|
284
|
+
topT: "┳",
|
|
285
|
+
bottomT: "┻",
|
|
286
|
+
},
|
|
287
|
+
none: {
|
|
288
|
+
topLeft: "",
|
|
289
|
+
topRight: "",
|
|
290
|
+
bottomLeft: "",
|
|
291
|
+
bottomRight: "",
|
|
292
|
+
horizontal: "",
|
|
293
|
+
vertical: "",
|
|
294
|
+
cross: "",
|
|
295
|
+
leftT: "",
|
|
296
|
+
rightT: "",
|
|
297
|
+
topT: "",
|
|
298
|
+
bottomT: "",
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Terminal table renderer
|
|
303
|
+
*/
|
|
304
|
+
export class Table {
|
|
305
|
+
columns;
|
|
306
|
+
data;
|
|
307
|
+
showHeader;
|
|
308
|
+
borderStyle;
|
|
309
|
+
rowSeparator;
|
|
310
|
+
caps;
|
|
311
|
+
constructor(options) {
|
|
312
|
+
this.caps = detectCapabilities();
|
|
313
|
+
this.columns = options.columns;
|
|
314
|
+
this.data = options.data;
|
|
315
|
+
this.showHeader = options.showHeader ?? true;
|
|
316
|
+
this.borderStyle = options.borderStyle ?? "simple";
|
|
317
|
+
this.rowSeparator = options.rowSeparator ?? false;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Render the table
|
|
321
|
+
*/
|
|
322
|
+
render() {
|
|
323
|
+
const borders = BorderChars[this.borderStyle];
|
|
324
|
+
const colWidths = this.calculateWidths();
|
|
325
|
+
const lines = [];
|
|
326
|
+
// Top border
|
|
327
|
+
if (this.borderStyle !== "none") {
|
|
328
|
+
lines.push(this.renderHorizontalLine(borders.topLeft, borders.topRight, borders.horizontal, borders.topT, colWidths));
|
|
329
|
+
}
|
|
330
|
+
// Header row
|
|
331
|
+
if (this.showHeader) {
|
|
332
|
+
const headerCells = this.columns.map((col, i) => {
|
|
333
|
+
const width = colWidths[i];
|
|
334
|
+
const text = this.truncate(col.header, width);
|
|
335
|
+
const aligned = this.align(text, width, col.align ?? "left");
|
|
336
|
+
const colored = col.headerColor && this.caps.colors256
|
|
337
|
+
? col.headerColor + aligned + Colors.reset
|
|
338
|
+
: aligned;
|
|
339
|
+
return colored;
|
|
340
|
+
});
|
|
341
|
+
lines.push(this.renderRow(headerCells, borders.vertical));
|
|
342
|
+
// Header separator
|
|
343
|
+
if (this.borderStyle !== "none") {
|
|
344
|
+
lines.push(this.renderHorizontalLine(borders.leftT, borders.rightT, borders.horizontal, borders.cross, colWidths));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// Data rows
|
|
348
|
+
for (let i = 0; i < this.data.length; i++) {
|
|
349
|
+
const row = this.data[i];
|
|
350
|
+
const cells = row.map((cell, j) => {
|
|
351
|
+
const width = colWidths[j];
|
|
352
|
+
const text = this.truncate(cell ?? "", width);
|
|
353
|
+
return this.align(text, width, this.columns[j]?.align ?? "left");
|
|
354
|
+
});
|
|
355
|
+
lines.push(this.renderRow(cells, borders.vertical));
|
|
356
|
+
// Row separator
|
|
357
|
+
if (this.rowSeparator && i < this.data.length - 1 && this.borderStyle !== "none") {
|
|
358
|
+
lines.push(this.renderHorizontalLine(borders.leftT, borders.rightT, borders.horizontal, borders.cross, colWidths));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Bottom border
|
|
362
|
+
if (this.borderStyle !== "none") {
|
|
363
|
+
lines.push(this.renderHorizontalLine(borders.bottomLeft, borders.bottomRight, borders.horizontal, borders.bottomT, colWidths));
|
|
364
|
+
}
|
|
365
|
+
return lines.join("\n");
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Print the table to stdout
|
|
369
|
+
*/
|
|
370
|
+
print() {
|
|
371
|
+
console.log(this.render());
|
|
372
|
+
}
|
|
373
|
+
calculateWidths() {
|
|
374
|
+
const caps = detectCapabilities();
|
|
375
|
+
const maxWidth = caps.width - 4; // Padding for borders
|
|
376
|
+
return this.columns.map((col, i) => {
|
|
377
|
+
if (col.width)
|
|
378
|
+
return col.width;
|
|
379
|
+
// Calculate based on content
|
|
380
|
+
const headerLen = col.header.length;
|
|
381
|
+
const maxDataLen = this.data.reduce((max, row) => {
|
|
382
|
+
const cellLen = (row[i] ?? "").length;
|
|
383
|
+
return Math.max(max, cellLen);
|
|
384
|
+
}, 0);
|
|
385
|
+
return Math.min(Math.max(headerLen, maxDataLen) + 2, Math.floor(maxWidth / this.columns.length));
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
renderHorizontalLine(left, right, horizontal, cross, widths) {
|
|
389
|
+
const segments = widths.map(w => horizontal.repeat(w));
|
|
390
|
+
return left + segments.join(cross) + right;
|
|
391
|
+
}
|
|
392
|
+
renderRow(cells, vertical) {
|
|
393
|
+
return vertical + cells.join(vertical) + vertical;
|
|
394
|
+
}
|
|
395
|
+
truncate(text, width) {
|
|
396
|
+
if (text.length <= width)
|
|
397
|
+
return text;
|
|
398
|
+
return text.slice(0, width - 3) + "...";
|
|
399
|
+
}
|
|
400
|
+
align(text, width, align) {
|
|
401
|
+
const padding = width - text.length;
|
|
402
|
+
switch (align) {
|
|
403
|
+
case "center": {
|
|
404
|
+
const left = Math.floor(padding / 2);
|
|
405
|
+
const right = padding - left;
|
|
406
|
+
return " ".repeat(left) + text + " ".repeat(right);
|
|
407
|
+
}
|
|
408
|
+
case "right":
|
|
409
|
+
return " ".repeat(padding) + text;
|
|
410
|
+
case "left":
|
|
411
|
+
default:
|
|
412
|
+
return text + " ".repeat(padding);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Interactive prompts
|
|
418
|
+
*/
|
|
419
|
+
export class Prompts {
|
|
420
|
+
/**
|
|
421
|
+
* Confirm yes/no prompt
|
|
422
|
+
*/
|
|
423
|
+
static async confirm(options) {
|
|
424
|
+
const defaultText = options.default === false ? "y/N" : "Y/n";
|
|
425
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
426
|
+
try {
|
|
427
|
+
const answer = await new Promise((resolve) => {
|
|
428
|
+
rl.question(`${options.message} [${defaultText}] `, resolve);
|
|
429
|
+
});
|
|
430
|
+
const normalized = answer.trim().toLowerCase();
|
|
431
|
+
if (normalized === "")
|
|
432
|
+
return options.default ?? true;
|
|
433
|
+
return normalized === "y" || normalized === "yes";
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
rl.close();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Text input prompt
|
|
441
|
+
*/
|
|
442
|
+
static async input(options) {
|
|
443
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
444
|
+
try {
|
|
445
|
+
const prompt = options.default
|
|
446
|
+
? `${options.message} (${options.default}) `
|
|
447
|
+
: `${options.message} `;
|
|
448
|
+
const answer = await new Promise((resolve) => {
|
|
449
|
+
rl.question(prompt, resolve);
|
|
450
|
+
});
|
|
451
|
+
const value = answer.trim() || options.default || "";
|
|
452
|
+
if (options.validate) {
|
|
453
|
+
const result = options.validate(value);
|
|
454
|
+
if (result !== true) {
|
|
455
|
+
console.log(colorize(`Error: ${result}`, Colors.red));
|
|
456
|
+
return Prompts.input(options);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return value;
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
rl.close();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Select from list prompt
|
|
467
|
+
*/
|
|
468
|
+
static async select(options) {
|
|
469
|
+
const available = options.choices.filter(c => !c.disabled);
|
|
470
|
+
console.log(options.message);
|
|
471
|
+
available.forEach((choice, i) => {
|
|
472
|
+
const marker = choice.value === options.default ? colorize("›", Colors.cyan) : " ";
|
|
473
|
+
console.log(` ${marker} ${i + 1}) ${choice.name}`);
|
|
474
|
+
});
|
|
475
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
476
|
+
try {
|
|
477
|
+
const answer = await new Promise((resolve) => {
|
|
478
|
+
rl.question("Select: ", resolve);
|
|
479
|
+
});
|
|
480
|
+
const num = parseInt(answer.trim(), 10);
|
|
481
|
+
if (num >= 1 && num <= available.length) {
|
|
482
|
+
return available[num - 1].value;
|
|
483
|
+
}
|
|
484
|
+
// Try matching by value
|
|
485
|
+
const match = available.find(c => c.value.toLowerCase() === answer.trim().toLowerCase());
|
|
486
|
+
if (match)
|
|
487
|
+
return match.value;
|
|
488
|
+
console.log(colorize("Invalid selection", Colors.red));
|
|
489
|
+
return Prompts.select(options);
|
|
490
|
+
}
|
|
491
|
+
finally {
|
|
492
|
+
rl.close();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Status indicators
|
|
498
|
+
*/
|
|
499
|
+
export const Status = {
|
|
500
|
+
success: colorize("✓", Colors.green),
|
|
501
|
+
error: colorize("✗", Colors.red),
|
|
502
|
+
warning: colorize("⚠", Colors.yellow),
|
|
503
|
+
info: colorize("ℹ", Colors.blue),
|
|
504
|
+
pending: colorize("○", Colors.gray),
|
|
505
|
+
};
|
|
506
|
+
/**
|
|
507
|
+
* Log a message with a status indicator
|
|
508
|
+
*/
|
|
509
|
+
export function logStatus(status, message) {
|
|
510
|
+
console.log(`${Status[status]} ${message}`);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Draw a box around content
|
|
514
|
+
*/
|
|
515
|
+
export function box(content, options = {}) {
|
|
516
|
+
const caps = detectCapabilities();
|
|
517
|
+
const lines = content.split("\n");
|
|
518
|
+
const maxLineLen = Math.max(...lines.map(l => l.length));
|
|
519
|
+
const width = options.width ?? maxLineLen + 4;
|
|
520
|
+
const padding = options.padding ?? 1;
|
|
521
|
+
const borders = {
|
|
522
|
+
simple: { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" },
|
|
523
|
+
rounded: { tl: "╭", tr: "╮", bl: "╰", br: "╯", h: "─", v: "│" },
|
|
524
|
+
heavy: { tl: "┏", tr: "┓", bl: "┗", br: "┛", h: "━", v: "┃" },
|
|
525
|
+
double: { tl: "╔", tr: "╗", bl: "╚", br: "╝", h: "═", v: "║" },
|
|
526
|
+
}[options.style ?? "rounded"];
|
|
527
|
+
const result = [];
|
|
528
|
+
// Top border with title
|
|
529
|
+
let topBorder = borders.tl + borders.h.repeat(width - 2) + borders.tr;
|
|
530
|
+
if (options.title) {
|
|
531
|
+
const title = caps.colors256 && options.titleColor
|
|
532
|
+
? options.titleColor + options.title + Colors.reset
|
|
533
|
+
: options.title;
|
|
534
|
+
const titleWithPadding = ` ${title} `;
|
|
535
|
+
const before = Math.floor((width - titleWithPadding.length) / 2);
|
|
536
|
+
topBorder = borders.tl + borders.h.repeat(before) + titleWithPadding + borders.h.repeat(width - before - titleWithPadding.length - 2) + borders.tr;
|
|
537
|
+
}
|
|
538
|
+
result.push(topBorder);
|
|
539
|
+
// Padding top
|
|
540
|
+
for (let i = 0; i < padding; i++) {
|
|
541
|
+
result.push(borders.v + " ".repeat(width - 2) + borders.v);
|
|
542
|
+
}
|
|
543
|
+
// Content
|
|
544
|
+
for (const line of lines) {
|
|
545
|
+
const contentWidth = Math.max(0, width - line.length - 2 - padding * 2);
|
|
546
|
+
const padded = " ".repeat(padding) + line + " ".repeat(contentWidth);
|
|
547
|
+
result.push(borders.v + padded + borders.v);
|
|
548
|
+
}
|
|
549
|
+
// Padding bottom
|
|
550
|
+
for (let i = 0; i < padding; i++) {
|
|
551
|
+
result.push(borders.v + " ".repeat(width - 2) + borders.v);
|
|
552
|
+
}
|
|
553
|
+
// Bottom border
|
|
554
|
+
result.push(borders.bl + borders.h.repeat(width - 2) + borders.br);
|
|
555
|
+
return result.join("\n");
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Create a multi-step wizard
|
|
559
|
+
*/
|
|
560
|
+
export class Wizard {
|
|
561
|
+
steps = [];
|
|
562
|
+
_currentStep = 0;
|
|
563
|
+
/**
|
|
564
|
+
* Add a step to the wizard
|
|
565
|
+
*/
|
|
566
|
+
step(name, run) {
|
|
567
|
+
this.steps.push({ name, run });
|
|
568
|
+
return this;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Run the wizard
|
|
572
|
+
*/
|
|
573
|
+
async run() {
|
|
574
|
+
console.log(box("Starting Setup Wizard", { title: "Wizard", style: "rounded" }));
|
|
575
|
+
for (let i = 0; i < this.steps.length; i++) {
|
|
576
|
+
this._currentStep = i;
|
|
577
|
+
const step = this.steps[i];
|
|
578
|
+
console.log(`\n${colorize(`Step ${i + 1}/${this.steps.length}:`, Colors.cyan)} ${step.name}`);
|
|
579
|
+
try {
|
|
580
|
+
const success = await step.run();
|
|
581
|
+
if (!success) {
|
|
582
|
+
logStatus("error", `Step "${step.name}" failed`);
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
logStatus("success", `Step "${step.name}" completed`);
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
logStatus("error", `Step "${step.name}" error: ${error instanceof Error ? error.message : String(error)}`);
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
console.log("\n" + box("Wizard completed successfully!", { style: "rounded", title: "✓ Complete" }));
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
}
|