@claude-flow/cli 3.0.0-alpha.1

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