@claude-flow/cli 3.0.0-alpha.13 → 3.0.0-alpha.14
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/package.json +12 -4
- package/.agentic-flow/intelligence.json +0 -17
- package/.claude-flow/agents/store.json +0 -16
- package/.claude-flow/daemon-state.json +0 -123
- package/.claude-flow/daemon-test.log +0 -0
- package/.claude-flow/daemon.log +0 -0
- package/.claude-flow/daemon2.log +0 -0
- package/.claude-flow/daemon3.log +0 -0
- package/.claude-flow/hive-mind/state.json +0 -51
- package/.claude-flow/metrics/agent-metrics.json +0 -1
- package/.claude-flow/metrics/codebase-map.json +0 -11
- package/.claude-flow/metrics/consolidation.json +0 -6
- package/.claude-flow/metrics/performance.json +0 -87
- package/.claude-flow/metrics/security-audit.json +0 -10
- package/.claude-flow/metrics/task-metrics.json +0 -10
- package/.claude-flow/metrics/test-gaps.json +0 -6
- package/__tests__/README.md +0 -140
- package/__tests__/TEST_SUMMARY.md +0 -144
- package/__tests__/cli.test.ts +0 -558
- package/__tests__/commands.test.ts +0 -726
- package/__tests__/config-adapter.test.ts +0 -362
- package/__tests__/config-loading.test.ts +0 -106
- package/__tests__/coverage/.tmp/coverage-0.json +0 -1
- package/__tests__/coverage/.tmp/coverage-1.json +0 -1
- package/__tests__/coverage/.tmp/coverage-2.json +0 -1
- package/__tests__/coverage/.tmp/coverage-3.json +0 -1
- package/__tests__/coverage/.tmp/coverage-4.json +0 -1
- package/__tests__/coverage/.tmp/coverage-5.json +0 -1
- package/__tests__/mcp-client.test.ts +0 -480
- package/__tests__/p1-commands.test.ts +0 -1064
- package/agents/architect.yaml +0 -11
- package/agents/coder.yaml +0 -11
- package/agents/reviewer.yaml +0 -10
- package/agents/security-architect.yaml +0 -10
- package/agents/tester.yaml +0 -10
- package/docs/CONFIG_LOADING.md +0 -236
- package/docs/IMPLEMENTATION_COMPLETE.md +0 -421
- package/docs/MCP_CLIENT_GUIDE.md +0 -620
- package/docs/REFACTORING_SUMMARY.md +0 -247
- package/scripts/publish.sh +0 -46
- package/src/commands/agent.ts +0 -955
- package/src/commands/claims.ts +0 -317
- package/src/commands/completions.ts +0 -558
- package/src/commands/config.ts +0 -452
- package/src/commands/daemon.ts +0 -621
- package/src/commands/deployment.ts +0 -323
- package/src/commands/doctor.ts +0 -382
- package/src/commands/embeddings.ts +0 -686
- package/src/commands/hive-mind.ts +0 -928
- package/src/commands/hooks.ts +0 -2603
- package/src/commands/index.ts +0 -154
- package/src/commands/init.ts +0 -597
- package/src/commands/mcp.ts +0 -753
- package/src/commands/memory.ts +0 -1161
- package/src/commands/migrate.ts +0 -447
- package/src/commands/neural.ts +0 -253
- package/src/commands/performance.ts +0 -292
- package/src/commands/plugins.ts +0 -316
- package/src/commands/process.ts +0 -695
- package/src/commands/providers.ts +0 -259
- package/src/commands/security.ts +0 -288
- package/src/commands/session.ts +0 -891
- package/src/commands/start.ts +0 -457
- package/src/commands/status.ts +0 -736
- package/src/commands/swarm.ts +0 -648
- package/src/commands/task.ts +0 -792
- package/src/commands/workflow.ts +0 -742
- package/src/config-adapter.ts +0 -210
- package/src/index.ts +0 -443
- package/src/infrastructure/in-memory-repositories.ts +0 -310
- package/src/init/claudemd-generator.ts +0 -631
- package/src/init/executor.ts +0 -762
- package/src/init/helpers-generator.ts +0 -628
- package/src/init/index.ts +0 -60
- package/src/init/mcp-generator.ts +0 -83
- package/src/init/settings-generator.ts +0 -284
- package/src/init/statusline-generator.ts +0 -211
- package/src/init/types.ts +0 -447
- package/src/mcp-client.ts +0 -241
- package/src/mcp-server.ts +0 -577
- package/src/mcp-tools/agent-tools.ts +0 -466
- package/src/mcp-tools/config-tools.ts +0 -370
- package/src/mcp-tools/hive-mind-tools.ts +0 -521
- package/src/mcp-tools/hooks-tools.ts +0 -1888
- package/src/mcp-tools/index.ts +0 -16
- package/src/mcp-tools/memory-tools.ts +0 -270
- package/src/mcp-tools/session-tools.ts +0 -359
- package/src/mcp-tools/swarm-tools.ts +0 -105
- package/src/mcp-tools/task-tools.ts +0 -347
- package/src/mcp-tools/types.ts +0 -33
- package/src/mcp-tools/workflow-tools.ts +0 -573
- package/src/output.ts +0 -639
- package/src/parser.ts +0 -417
- package/src/prompt.ts +0 -619
- package/src/services/index.ts +0 -15
- package/src/services/worker-daemon.ts +0 -726
- package/src/suggest.ts +0 -245
- package/src/types.ts +0 -287
- package/tmp.json +0 -0
- package/tsconfig.json +0 -16
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -13
package/src/output.ts
DELETED
|
@@ -1,639 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* V3 CLI Output Formatter
|
|
3
|
-
* Advanced output formatting with tables, progress bars, and colors
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { TableOptions, TableColumn, ProgressOptions, SpinnerOptions } from './types.js';
|
|
7
|
-
|
|
8
|
-
// ============================================
|
|
9
|
-
// Color Support
|
|
10
|
-
// ============================================
|
|
11
|
-
|
|
12
|
-
const COLORS = {
|
|
13
|
-
// Standard colors
|
|
14
|
-
reset: '\x1b[0m',
|
|
15
|
-
bold: '\x1b[1m',
|
|
16
|
-
dim: '\x1b[2m',
|
|
17
|
-
italic: '\x1b[3m',
|
|
18
|
-
underline: '\x1b[4m',
|
|
19
|
-
|
|
20
|
-
// Foreground colors
|
|
21
|
-
black: '\x1b[30m',
|
|
22
|
-
red: '\x1b[31m',
|
|
23
|
-
green: '\x1b[32m',
|
|
24
|
-
yellow: '\x1b[33m',
|
|
25
|
-
blue: '\x1b[34m',
|
|
26
|
-
magenta: '\x1b[35m',
|
|
27
|
-
cyan: '\x1b[36m',
|
|
28
|
-
white: '\x1b[37m',
|
|
29
|
-
gray: '\x1b[90m',
|
|
30
|
-
|
|
31
|
-
// Bright foreground colors
|
|
32
|
-
brightRed: '\x1b[91m',
|
|
33
|
-
brightGreen: '\x1b[92m',
|
|
34
|
-
brightYellow: '\x1b[93m',
|
|
35
|
-
brightBlue: '\x1b[94m',
|
|
36
|
-
brightMagenta: '\x1b[95m',
|
|
37
|
-
brightCyan: '\x1b[96m',
|
|
38
|
-
brightWhite: '\x1b[97m',
|
|
39
|
-
|
|
40
|
-
// Background colors
|
|
41
|
-
bgBlack: '\x1b[40m',
|
|
42
|
-
bgRed: '\x1b[41m',
|
|
43
|
-
bgGreen: '\x1b[42m',
|
|
44
|
-
bgYellow: '\x1b[43m',
|
|
45
|
-
bgBlue: '\x1b[44m',
|
|
46
|
-
bgMagenta: '\x1b[45m',
|
|
47
|
-
bgCyan: '\x1b[46m',
|
|
48
|
-
bgWhite: '\x1b[47m'
|
|
49
|
-
} as const;
|
|
50
|
-
|
|
51
|
-
type ColorName = keyof typeof COLORS;
|
|
52
|
-
|
|
53
|
-
export type VerbosityLevel = 'quiet' | 'normal' | 'verbose' | 'debug';
|
|
54
|
-
|
|
55
|
-
export class OutputFormatter {
|
|
56
|
-
private colorEnabled: boolean;
|
|
57
|
-
private outputStream: NodeJS.WriteStream;
|
|
58
|
-
private errorStream: NodeJS.WriteStream;
|
|
59
|
-
private verbosity: VerbosityLevel;
|
|
60
|
-
|
|
61
|
-
constructor(options: { color?: boolean; verbosity?: VerbosityLevel } = {}) {
|
|
62
|
-
this.colorEnabled = options.color ?? this.supportsColor();
|
|
63
|
-
this.outputStream = process.stdout;
|
|
64
|
-
this.errorStream = process.stderr;
|
|
65
|
-
this.verbosity = options.verbosity ?? 'normal';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Set verbosity level
|
|
70
|
-
* - quiet: Only errors and direct results
|
|
71
|
-
* - normal: Errors, warnings, info, and results
|
|
72
|
-
* - verbose: All of normal + debug messages
|
|
73
|
-
* - debug: All output including trace
|
|
74
|
-
*/
|
|
75
|
-
setVerbosity(level: VerbosityLevel): void {
|
|
76
|
-
this.verbosity = level;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
getVerbosity(): VerbosityLevel {
|
|
80
|
-
return this.verbosity;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
isQuiet(): boolean {
|
|
84
|
-
return this.verbosity === 'quiet';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
isVerbose(): boolean {
|
|
88
|
-
return this.verbosity === 'verbose' || this.verbosity === 'debug';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
isDebug(): boolean {
|
|
92
|
-
return this.verbosity === 'debug';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
private supportsColor(): boolean {
|
|
96
|
-
// Check for NO_COLOR environment variable
|
|
97
|
-
if (process.env.NO_COLOR !== undefined) return false;
|
|
98
|
-
|
|
99
|
-
// Check for FORCE_COLOR environment variable
|
|
100
|
-
if (process.env.FORCE_COLOR !== undefined) return true;
|
|
101
|
-
|
|
102
|
-
// Check if stdout is a TTY
|
|
103
|
-
return process.stdout.isTTY ?? false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ============================================
|
|
107
|
-
// Color Methods
|
|
108
|
-
// ============================================
|
|
109
|
-
|
|
110
|
-
color(text: string, ...colors: ColorName[]): string {
|
|
111
|
-
if (!this.colorEnabled) return text;
|
|
112
|
-
|
|
113
|
-
const codes = colors.map(c => COLORS[c]).join('');
|
|
114
|
-
return `${codes}${text}${COLORS.reset}`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
bold(text: string): string {
|
|
118
|
-
return this.color(text, 'bold');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
dim(text: string): string {
|
|
122
|
-
return this.color(text, 'dim');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
success(text: string): string {
|
|
126
|
-
return this.color(text, 'green');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
error(text: string): string {
|
|
130
|
-
return this.color(text, 'red');
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
warning(text: string): string {
|
|
134
|
-
return this.color(text, 'yellow');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
info(text: string): string {
|
|
138
|
-
return this.color(text, 'blue');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
highlight(text: string): string {
|
|
142
|
-
return this.color(text, 'cyan', 'bold');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ============================================
|
|
146
|
-
// Output Methods
|
|
147
|
-
// ============================================
|
|
148
|
-
|
|
149
|
-
write(text: string): void {
|
|
150
|
-
this.outputStream.write(text);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
writeln(text: string = ''): void {
|
|
154
|
-
this.outputStream.write(text + '\n');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
writeError(text: string): void {
|
|
158
|
-
this.errorStream.write(text);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
writeErrorln(text: string = ''): void {
|
|
162
|
-
this.errorStream.write(text + '\n');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ============================================
|
|
166
|
-
// Formatted Output Methods
|
|
167
|
-
// ============================================
|
|
168
|
-
|
|
169
|
-
printSuccess(message: string): void {
|
|
170
|
-
// Success always shows (result output)
|
|
171
|
-
const icon = this.color('[OK]', 'green', 'bold');
|
|
172
|
-
this.writeln(`${icon} ${message}`);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
printError(message: string, details?: string): void {
|
|
176
|
-
// Errors always show
|
|
177
|
-
const icon = this.color('[ERROR]', 'red', 'bold');
|
|
178
|
-
this.writeErrorln(`${icon} ${message}`);
|
|
179
|
-
if (details) {
|
|
180
|
-
this.writeErrorln(this.dim(` ${details}`));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
printWarning(message: string): void {
|
|
185
|
-
// Warnings suppressed in quiet mode
|
|
186
|
-
if (this.verbosity === 'quiet') return;
|
|
187
|
-
const icon = this.color('[WARN]', 'yellow', 'bold');
|
|
188
|
-
this.writeln(`${icon} ${message}`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
printInfo(message: string): void {
|
|
192
|
-
// Info suppressed in quiet mode
|
|
193
|
-
if (this.verbosity === 'quiet') return;
|
|
194
|
-
const icon = this.color('[INFO]', 'blue', 'bold');
|
|
195
|
-
this.writeln(`${icon} ${message}`);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
printDebug(message: string): void {
|
|
199
|
-
// Debug only shows in verbose/debug mode
|
|
200
|
-
if (this.verbosity !== 'verbose' && this.verbosity !== 'debug') return;
|
|
201
|
-
const icon = this.color('[DEBUG]', 'gray');
|
|
202
|
-
this.writeln(`${icon} ${this.dim(message)}`);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
printTrace(message: string): void {
|
|
206
|
-
// Trace only shows in debug mode
|
|
207
|
-
if (this.verbosity !== 'debug') return;
|
|
208
|
-
const icon = this.color('[TRACE]', 'gray', 'dim');
|
|
209
|
-
this.writeln(`${icon} ${this.dim(message)}`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ============================================
|
|
213
|
-
// Table Formatting
|
|
214
|
-
// ============================================
|
|
215
|
-
|
|
216
|
-
table(options: TableOptions): string {
|
|
217
|
-
const { columns, data, border = true, header = true, padding = 1, maxWidth } = options;
|
|
218
|
-
|
|
219
|
-
// Calculate column widths
|
|
220
|
-
const widths = this.calculateColumnWidths(columns, data, maxWidth);
|
|
221
|
-
|
|
222
|
-
const lines: string[] = [];
|
|
223
|
-
const pad = ' '.repeat(padding);
|
|
224
|
-
|
|
225
|
-
// Border characters
|
|
226
|
-
const borderChars = border ? {
|
|
227
|
-
topLeft: '+', topRight: '+', bottomLeft: '+', bottomRight: '+',
|
|
228
|
-
horizontal: '-', vertical: '|',
|
|
229
|
-
leftT: '+', rightT: '+', topT: '+', bottomT: '+', cross: '+'
|
|
230
|
-
} : {
|
|
231
|
-
topLeft: '', topRight: '', bottomLeft: '', bottomRight: '',
|
|
232
|
-
horizontal: '', vertical: ' ',
|
|
233
|
-
leftT: '', rightT: '', topT: '', bottomT: '', cross: ''
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Top border
|
|
237
|
-
if (border) {
|
|
238
|
-
lines.push(this.createBorderLine(widths, borderChars, 'top', padding));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Header row
|
|
242
|
-
if (header) {
|
|
243
|
-
const headerRow = columns.map((col, i) => {
|
|
244
|
-
const text = this.truncate(col.header, widths[i]);
|
|
245
|
-
return pad + this.alignText(this.bold(text), widths[i], col.align) + pad;
|
|
246
|
-
}).join(borderChars.vertical);
|
|
247
|
-
|
|
248
|
-
lines.push(`${borderChars.vertical}${headerRow}${borderChars.vertical}`);
|
|
249
|
-
|
|
250
|
-
// Header separator
|
|
251
|
-
if (border) {
|
|
252
|
-
lines.push(this.createBorderLine(widths, borderChars, 'middle', padding));
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Data rows
|
|
257
|
-
for (const row of data) {
|
|
258
|
-
const rowCells = columns.map((col, i) => {
|
|
259
|
-
let value = row[col.key];
|
|
260
|
-
|
|
261
|
-
// Apply formatter if provided
|
|
262
|
-
if (col.format) {
|
|
263
|
-
value = col.format(value);
|
|
264
|
-
} else {
|
|
265
|
-
value = String(value ?? '');
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const text = this.truncate(String(value), widths[i]);
|
|
269
|
-
return pad + this.alignText(text, widths[i], col.align) + pad;
|
|
270
|
-
}).join(borderChars.vertical);
|
|
271
|
-
|
|
272
|
-
lines.push(`${borderChars.vertical}${rowCells}${borderChars.vertical}`);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Bottom border
|
|
276
|
-
if (border) {
|
|
277
|
-
lines.push(this.createBorderLine(widths, borderChars, 'bottom', padding));
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return lines.join('\n');
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
printTable(options: TableOptions): void {
|
|
284
|
-
this.writeln(this.table(options));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
private calculateColumnWidths(
|
|
288
|
-
columns: TableColumn[],
|
|
289
|
-
data: Record<string, unknown>[],
|
|
290
|
-
maxWidth?: number
|
|
291
|
-
): number[] {
|
|
292
|
-
const widths = columns.map((col, i) => {
|
|
293
|
-
// Start with header width
|
|
294
|
-
let width = col.header.length;
|
|
295
|
-
|
|
296
|
-
// Check all data values
|
|
297
|
-
for (const row of data) {
|
|
298
|
-
let value = row[col.key];
|
|
299
|
-
if (col.format) {
|
|
300
|
-
value = col.format(value);
|
|
301
|
-
}
|
|
302
|
-
const len = this.stripAnsi(String(value ?? '')).length;
|
|
303
|
-
width = Math.max(width, len);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Apply column-specific width limit
|
|
307
|
-
if (col.width) {
|
|
308
|
-
width = Math.min(width, col.width);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return width;
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
// Apply max width constraint
|
|
315
|
-
if (maxWidth) {
|
|
316
|
-
const totalWidth = widths.reduce((a, b) => a + b, 0) + (columns.length * 3) + 1;
|
|
317
|
-
if (totalWidth > maxWidth) {
|
|
318
|
-
const reduction = (totalWidth - maxWidth) / columns.length;
|
|
319
|
-
return widths.map(w => Math.max(3, Math.floor(w - reduction)));
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return widths;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
private createBorderLine(
|
|
327
|
-
widths: number[],
|
|
328
|
-
chars: Record<string, string>,
|
|
329
|
-
position: 'top' | 'middle' | 'bottom',
|
|
330
|
-
padding: number
|
|
331
|
-
): string {
|
|
332
|
-
const cellWidth = (w: number) => chars.horizontal.repeat(w + (padding * 2));
|
|
333
|
-
const cells = widths.map(cellWidth).join(
|
|
334
|
-
position === 'top' ? chars.topT :
|
|
335
|
-
position === 'bottom' ? chars.bottomT :
|
|
336
|
-
chars.cross
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
const left = position === 'top' ? chars.topLeft : position === 'bottom' ? chars.bottomLeft : chars.leftT;
|
|
340
|
-
const right = position === 'top' ? chars.topRight : position === 'bottom' ? chars.bottomRight : chars.rightT;
|
|
341
|
-
|
|
342
|
-
return `${left}${cells}${right}`;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
private alignText(text: string, width: number, align: 'left' | 'center' | 'right' = 'left'): string {
|
|
346
|
-
const len = this.stripAnsi(text).length;
|
|
347
|
-
const padding = width - len;
|
|
348
|
-
|
|
349
|
-
if (padding <= 0) return text;
|
|
350
|
-
|
|
351
|
-
switch (align) {
|
|
352
|
-
case 'right':
|
|
353
|
-
return ' '.repeat(padding) + text;
|
|
354
|
-
case 'center':
|
|
355
|
-
const left = Math.floor(padding / 2);
|
|
356
|
-
const right = padding - left;
|
|
357
|
-
return ' '.repeat(left) + text + ' '.repeat(right);
|
|
358
|
-
default:
|
|
359
|
-
return text + ' '.repeat(padding);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
private truncate(text: string, maxLength: number): string {
|
|
364
|
-
const stripped = this.stripAnsi(text);
|
|
365
|
-
if (stripped.length <= maxLength) return text;
|
|
366
|
-
return stripped.slice(0, maxLength - 3) + '...';
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
private stripAnsi(text: string): string {
|
|
370
|
-
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// ============================================
|
|
374
|
-
// Progress Bar
|
|
375
|
-
// ============================================
|
|
376
|
-
|
|
377
|
-
createProgress(options: ProgressOptions): Progress {
|
|
378
|
-
return new Progress(this, options);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
progressBar(current: number, total: number, width: number = 40): string {
|
|
382
|
-
const percent = Math.min(100, Math.max(0, (current / total) * 100));
|
|
383
|
-
const filled = Math.round((width * percent) / 100);
|
|
384
|
-
const empty = width - filled;
|
|
385
|
-
|
|
386
|
-
const bar = this.color('#'.repeat(filled), 'green') +
|
|
387
|
-
this.dim('-'.repeat(empty));
|
|
388
|
-
|
|
389
|
-
return `[${bar}] ${percent.toFixed(1)}%`;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// ============================================
|
|
393
|
-
// Spinner
|
|
394
|
-
// ============================================
|
|
395
|
-
|
|
396
|
-
createSpinner(options: SpinnerOptions): Spinner {
|
|
397
|
-
return new Spinner(this, options);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// ============================================
|
|
401
|
-
// JSON Output
|
|
402
|
-
// ============================================
|
|
403
|
-
|
|
404
|
-
json(data: unknown, pretty: boolean = true): string {
|
|
405
|
-
return pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
printJson(data: unknown, pretty: boolean = true): void {
|
|
409
|
-
this.writeln(this.json(data, pretty));
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// ============================================
|
|
413
|
-
// List Output
|
|
414
|
-
// ============================================
|
|
415
|
-
|
|
416
|
-
list(items: string[], bullet: string = '-'): string {
|
|
417
|
-
return items.map(item => ` ${bullet} ${item}`).join('\n');
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
printList(items: string[], bullet: string = '-'): void {
|
|
421
|
-
this.writeln(this.list(items, bullet));
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
numberedList(items: string[]): string {
|
|
425
|
-
return items.map((item, i) => ` ${i + 1}. ${item}`).join('\n');
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
printNumberedList(items: string[]): void {
|
|
429
|
-
this.writeln(this.numberedList(items));
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// ============================================
|
|
433
|
-
// Box Output
|
|
434
|
-
// ============================================
|
|
435
|
-
|
|
436
|
-
box(content: string, title?: string): string {
|
|
437
|
-
const lines = content.split('\n');
|
|
438
|
-
const maxLen = Math.max(...lines.map(l => this.stripAnsi(l).length), title?.length ?? 0);
|
|
439
|
-
const width = maxLen + 4;
|
|
440
|
-
|
|
441
|
-
const border = {
|
|
442
|
-
topLeft: '+', topRight: '+',
|
|
443
|
-
bottomLeft: '+', bottomRight: '+',
|
|
444
|
-
horizontal: '-', vertical: '|'
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
const result: string[] = [];
|
|
448
|
-
|
|
449
|
-
// Top border with optional title
|
|
450
|
-
if (title) {
|
|
451
|
-
const titleText = ` ${title} `;
|
|
452
|
-
const leftPad = Math.floor((width - titleText.length - 2) / 2);
|
|
453
|
-
const rightPad = width - titleText.length - leftPad - 2;
|
|
454
|
-
result.push(
|
|
455
|
-
border.topLeft +
|
|
456
|
-
border.horizontal.repeat(leftPad) +
|
|
457
|
-
this.bold(titleText) +
|
|
458
|
-
border.horizontal.repeat(rightPad) +
|
|
459
|
-
border.topRight
|
|
460
|
-
);
|
|
461
|
-
} else {
|
|
462
|
-
result.push(border.topLeft + border.horizontal.repeat(width - 2) + border.topRight);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Content lines
|
|
466
|
-
for (const line of lines) {
|
|
467
|
-
const stripped = this.stripAnsi(line);
|
|
468
|
-
const padding = maxLen - stripped.length;
|
|
469
|
-
result.push(`${border.vertical} ${line}${' '.repeat(padding)} ${border.vertical}`);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Bottom border
|
|
473
|
-
result.push(border.bottomLeft + border.horizontal.repeat(width - 2) + border.bottomRight);
|
|
474
|
-
|
|
475
|
-
return result.join('\n');
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
printBox(content: string, title?: string): void {
|
|
479
|
-
this.writeln(this.box(content, title));
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
setColorEnabled(enabled: boolean): void {
|
|
483
|
-
this.colorEnabled = enabled;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
isColorEnabled(): boolean {
|
|
487
|
-
return this.colorEnabled;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// ============================================
|
|
492
|
-
// Progress Class
|
|
493
|
-
// ============================================
|
|
494
|
-
|
|
495
|
-
export class Progress {
|
|
496
|
-
private current: number;
|
|
497
|
-
private total: number;
|
|
498
|
-
private width: number;
|
|
499
|
-
private startTime: number;
|
|
500
|
-
private formatter: OutputFormatter;
|
|
501
|
-
private showPercentage: boolean;
|
|
502
|
-
private showETA: boolean;
|
|
503
|
-
private lastRender: string = '';
|
|
504
|
-
|
|
505
|
-
constructor(formatter: OutputFormatter, options: ProgressOptions) {
|
|
506
|
-
this.formatter = formatter;
|
|
507
|
-
this.current = options.current ?? 0;
|
|
508
|
-
this.total = options.total;
|
|
509
|
-
this.width = options.width ?? 40;
|
|
510
|
-
this.showPercentage = options.showPercentage ?? true;
|
|
511
|
-
this.showETA = options.showETA ?? true;
|
|
512
|
-
this.startTime = Date.now();
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
update(current: number): void {
|
|
516
|
-
this.current = current;
|
|
517
|
-
this.render();
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
increment(amount: number = 1): void {
|
|
521
|
-
this.update(this.current + amount);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
render(): void {
|
|
525
|
-
const bar = this.formatter.progressBar(this.current, this.total, this.width);
|
|
526
|
-
|
|
527
|
-
let output = bar;
|
|
528
|
-
|
|
529
|
-
if (this.showETA && this.current > 0) {
|
|
530
|
-
const elapsed = Date.now() - this.startTime;
|
|
531
|
-
const rate = this.current / elapsed;
|
|
532
|
-
const remaining = this.total - this.current;
|
|
533
|
-
const eta = remaining / rate;
|
|
534
|
-
|
|
535
|
-
if (isFinite(eta)) {
|
|
536
|
-
output += ` ETA: ${this.formatTime(eta)}`;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Clear previous line and write new
|
|
541
|
-
if (this.lastRender) {
|
|
542
|
-
process.stdout.write('\r' + ' '.repeat(this.lastRender.length) + '\r');
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
process.stdout.write(output);
|
|
546
|
-
this.lastRender = output;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
finish(): void {
|
|
550
|
-
this.current = this.total;
|
|
551
|
-
this.render();
|
|
552
|
-
process.stdout.write('\n');
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
private formatTime(ms: number): string {
|
|
556
|
-
const seconds = Math.floor(ms / 1000);
|
|
557
|
-
const minutes = Math.floor(seconds / 60);
|
|
558
|
-
const hours = Math.floor(minutes / 60);
|
|
559
|
-
|
|
560
|
-
if (hours > 0) {
|
|
561
|
-
return `${hours}h ${minutes % 60}m`;
|
|
562
|
-
} else if (minutes > 0) {
|
|
563
|
-
return `${minutes}m ${seconds % 60}s`;
|
|
564
|
-
} else {
|
|
565
|
-
return `${seconds}s`;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// ============================================
|
|
571
|
-
// Spinner Class
|
|
572
|
-
// ============================================
|
|
573
|
-
|
|
574
|
-
export class Spinner {
|
|
575
|
-
private formatter: OutputFormatter;
|
|
576
|
-
private text: string;
|
|
577
|
-
private frames: string[];
|
|
578
|
-
private interval: ReturnType<typeof setInterval> | null = null;
|
|
579
|
-
private frameIndex: number = 0;
|
|
580
|
-
|
|
581
|
-
private static readonly SPINNERS: Record<string, string[]> = {
|
|
582
|
-
dots: ['...', '..:' , '.::', ':::', '::.', ':..' ,],
|
|
583
|
-
line: ['-', '\\', '|', '/'],
|
|
584
|
-
arc: ['◜', '◠', '◝', '◞', '◡', '◟'],
|
|
585
|
-
circle: ['◐', '◓', '◑', '◒'],
|
|
586
|
-
arrows: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙']
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
constructor(formatter: OutputFormatter, options: SpinnerOptions) {
|
|
590
|
-
this.formatter = formatter;
|
|
591
|
-
this.text = options.text;
|
|
592
|
-
this.frames = Spinner.SPINNERS[options.spinner ?? 'dots'];
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
start(): void {
|
|
596
|
-
if (this.interval) return;
|
|
597
|
-
|
|
598
|
-
this.interval = setInterval(() => {
|
|
599
|
-
this.render();
|
|
600
|
-
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
601
|
-
}, 100);
|
|
602
|
-
|
|
603
|
-
this.render();
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
stop(message?: string): void {
|
|
607
|
-
if (this.interval) {
|
|
608
|
-
clearInterval(this.interval);
|
|
609
|
-
this.interval = null;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// Clear the line
|
|
613
|
-
process.stdout.write('\r' + ' '.repeat(this.text.length + 10) + '\r');
|
|
614
|
-
|
|
615
|
-
if (message) {
|
|
616
|
-
this.formatter.writeln(message);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
succeed(message?: string): void {
|
|
621
|
-
this.stop(this.formatter.success(message ?? this.text));
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
fail(message?: string): void {
|
|
625
|
-
this.stop(this.formatter.error(message ?? this.text));
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
private render(): void {
|
|
629
|
-
const frame = this.formatter.info(this.frames[this.frameIndex]);
|
|
630
|
-
process.stdout.write(`\r${frame} ${this.text}`);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
setText(text: string): void {
|
|
634
|
-
this.text = text;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// Export singleton instance
|
|
639
|
-
export const output = new OutputFormatter();
|