claude-autopm 1.18.0 → 1.20.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.
- package/README.md +159 -0
- package/autopm/.claude/agents/README.md +1 -1
- package/autopm/.claude/agents/core/mcp-manager.md +1 -1
- package/autopm/.claude/agents/decision-matrices/python-backend-selection.md +25 -25
- package/autopm/.claude/agents/decision-matrices/ui-framework-selection.md +43 -43
- package/autopm/.claude/agents/devops/github-operations-specialist.md +1 -1
- package/autopm/.claude/agents/frameworks/README.md +5 -5
- package/autopm/.claude/agents/frameworks/e2e-test-engineer.md +1 -1
- package/autopm/.claude/agents/frameworks/nats-messaging-expert.md +1 -1
- package/autopm/.claude/agents/frameworks/react-frontend-engineer.md +1 -1
- package/autopm/.claude/agents/frameworks/react-ui-expert.md +3 -3
- package/autopm/.claude/agents/frameworks/tailwindcss-expert.md +3 -3
- package/autopm/.claude/agents/frameworks/ux-design-expert.md +3 -3
- package/autopm/.claude/commands/infrastructure/traefik-setup.md +1 -1
- package/autopm/.claude/commands/playwright/test-scaffold.md +1 -1
- package/autopm/.claude/commands/pm/context.md +11 -0
- package/autopm/.claude/commands/pm/epic-decompose.md +25 -2
- package/autopm/.claude/commands/pm/epic-oneshot.md +13 -0
- package/autopm/.claude/commands/pm/epic-start.md +19 -0
- package/autopm/.claude/commands/pm/epic-sync-modular.md +10 -10
- package/autopm/.claude/commands/pm/epic-sync.md +14 -14
- package/autopm/.claude/commands/pm/issue-start.md +50 -5
- package/autopm/.claude/commands/pm/issue-sync.md +15 -15
- package/autopm/.claude/commands/pm/what-next.md +11 -0
- package/autopm/.claude/commands/ui/bootstrap-scaffold.md +6 -5
- package/autopm/.claude/commands/ui/tailwind-system.md +1 -1
- package/autopm/.claude/examples/mcp/playwright-mcp.md +2 -2
- package/autopm/.claude/examples/mcp-servers.example.json +2 -2
- package/autopm/.claude/hooks/docker-first-enforcement.sh +1 -1
- package/autopm/.claude/mcp/MCP-REGISTRY.md +1 -1
- package/autopm/.claude/mcp/playwright-mcp.md +2 -2
- package/autopm/.claude/rules/agent-coordination.md +26 -24
- package/autopm/.claude/rules/docker-first-development.md +1 -1
- package/autopm/.claude/rules/infrastructure-pipeline.md +1 -1
- package/autopm/.claude/rules/ui-development-standards.md +1 -1
- package/autopm/.claude/rules/visual-testing.md +3 -3
- package/autopm/.claude/scripts/azure/active-work.js +2 -2
- package/autopm/.claude/scripts/azure/blocked.js +13 -13
- package/autopm/.claude/scripts/azure/daily.js +1 -1
- package/autopm/.claude/scripts/azure/dashboard.js +1 -1
- package/autopm/.claude/scripts/azure/feature-list.js +2 -2
- package/autopm/.claude/scripts/azure/feature-status.js +1 -1
- package/autopm/.claude/scripts/azure/next-task.js +1 -1
- package/autopm/.claude/scripts/azure/search.js +1 -1
- package/autopm/.claude/scripts/azure/setup.js +15 -15
- package/autopm/.claude/scripts/azure/sprint-report.js +2 -2
- package/autopm/.claude/scripts/azure/sync.js +1 -1
- package/autopm/.claude/scripts/azure/us-list.js +1 -1
- package/autopm/.claude/scripts/azure/us-status.js +1 -1
- package/autopm/.claude/scripts/azure/validate.js +13 -13
- package/autopm/.claude/scripts/lib/frontmatter-utils.sh +42 -7
- package/autopm/.claude/scripts/lib/logging-utils.sh +20 -16
- package/autopm/.claude/scripts/lib/validation-utils.sh +1 -1
- package/autopm/.claude/scripts/pm/context.js +338 -0
- package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +3 -3
- package/autopm/.claude/scripts/pm/lib/README.md +85 -0
- package/autopm/.claude/scripts/pm/lib/logger.js +78 -0
- package/autopm/.claude/scripts/pm/next.js +25 -1
- package/autopm/.claude/scripts/pm/what-next.js +660 -0
- package/autopm/.claude/teams.json +3 -5
- package/autopm/.claude/templates/claude-templates/addons/devops-agents.md +2 -2
- package/autopm/.claude/templates/claude-templates/addons/docker-agents.md +4 -4
- package/autopm/.claude/templates/claude-templates/addons/minimal-agents.md +1 -1
- package/autopm/.claude/templates/issue-decomposition/api.yaml +2 -2
- package/autopm/.claude/templates/issue-decomposition/auth.yaml +4 -4
- package/autopm/.claude/templates/issue-decomposition/crud.yaml +3 -3
- package/autopm/.claude/templates/issue-decomposition/default.yaml +1 -1
- package/autopm/.claude/templates/issue-decomposition/ui-feature.yaml +2 -2
- package/bin/autopm.js +25 -0
- package/package.json +1 -2
- package/lib/agentExecutor.js.deprecated +0 -101
- package/lib/azure/cache.js +0 -80
- package/lib/azure/client.js +0 -77
- package/lib/azure/formatter.js +0 -177
- package/lib/commandHelpers.js +0 -177
- package/lib/context/manager.js +0 -290
- package/lib/documentation/manager.js +0 -528
- package/lib/github/workflow-manager.js +0 -546
- package/lib/helpers/azure-batch-api.js +0 -133
- package/lib/helpers/azure-cache-manager.js +0 -287
- package/lib/helpers/azure-parallel-processor.js +0 -158
- package/lib/helpers/azure-work-item-create.js +0 -278
- package/lib/helpers/gh-issue-create.js +0 -250
- package/lib/helpers/interactive-prompt.js +0 -336
- package/lib/helpers/output-manager.js +0 -335
- package/lib/helpers/progress-indicator.js +0 -258
- package/lib/performance/benchmarker.js +0 -429
- package/lib/pm/epic-decomposer.js +0 -273
- package/lib/pm/epic-syncer.js +0 -221
- package/lib/prdMetadata.js +0 -270
- package/lib/providers/azure/index.js +0 -234
- package/lib/providers/factory.js +0 -87
- package/lib/providers/github/index.js +0 -204
- package/lib/providers/interface.js +0 -73
- package/lib/python/scaffold-manager.js +0 -576
- package/lib/react/scaffold-manager.js +0 -745
- package/lib/regression/analyzer.js +0 -578
- package/lib/release/manager.js +0 -324
- package/lib/tailwind/manager.js +0 -486
- package/lib/traefik/manager.js +0 -484
- package/lib/utils/colors.js +0 -126
- package/lib/utils/config.js +0 -317
- package/lib/utils/filesystem.js +0 -316
- package/lib/utils/logger.js +0 -135
- package/lib/utils/prompts.js +0 -294
- package/lib/utils/shell.js +0 -237
- package/lib/validators/email-validator.js +0 -337
- package/lib/workflow/manager.js +0 -449
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Output Manager
|
|
4
|
-
* Handles verbose/quiet output modes and formatting
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const chalk = require('chalk');
|
|
8
|
-
|
|
9
|
-
class OutputManager {
|
|
10
|
-
constructor(options = {}) {
|
|
11
|
-
this.mode = options.mode || process.env.OUTPUT_MODE || 'normal'; // quiet, normal, verbose, debug
|
|
12
|
-
this.useColor = options.color !== false && process.stdout.isTTY;
|
|
13
|
-
this.format = options.format || 'text'; // text, json, csv, table
|
|
14
|
-
this.logFile = options.logFile;
|
|
15
|
-
this.timestamps = options.timestamps || false;
|
|
16
|
-
|
|
17
|
-
// Set up chalk
|
|
18
|
-
if (!this.useColor) {
|
|
19
|
-
chalk.level = 0;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Output levels
|
|
23
|
-
this.levels = {
|
|
24
|
-
debug: 0,
|
|
25
|
-
verbose: 1,
|
|
26
|
-
normal: 2,
|
|
27
|
-
quiet: 3,
|
|
28
|
-
silent: 4
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
this.currentLevel = this.levels[this.mode] || 2;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Set output mode
|
|
36
|
-
*/
|
|
37
|
-
setMode(mode) {
|
|
38
|
-
this.mode = mode;
|
|
39
|
-
this.currentLevel = this.levels[mode] || 2;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Check if should output at level
|
|
44
|
-
*/
|
|
45
|
-
shouldOutput(level) {
|
|
46
|
-
return this.currentLevel <= this.levels[level];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Debug output (only in debug mode)
|
|
51
|
-
*/
|
|
52
|
-
debug(...args) {
|
|
53
|
-
if (this.shouldOutput('debug')) {
|
|
54
|
-
const message = this.formatMessage('[DEBUG]', ...args);
|
|
55
|
-
console.error(this.useColor ? chalk.gray(message) : message);
|
|
56
|
-
this.log(message);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Verbose output (verbose and debug modes)
|
|
62
|
-
*/
|
|
63
|
-
verbose(...args) {
|
|
64
|
-
if (this.shouldOutput('verbose')) {
|
|
65
|
-
const message = this.formatMessage('[VERBOSE]', ...args);
|
|
66
|
-
console.log(this.useColor ? chalk.dim(message) : message);
|
|
67
|
-
this.log(message);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Normal output (normal, verbose, and debug modes)
|
|
73
|
-
*/
|
|
74
|
-
info(...args) {
|
|
75
|
-
if (this.shouldOutput('normal')) {
|
|
76
|
-
const message = this.formatMessage(...args);
|
|
77
|
-
console.log(message);
|
|
78
|
-
this.log(message);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Important output (all modes except silent)
|
|
84
|
-
*/
|
|
85
|
-
important(...args) {
|
|
86
|
-
if (this.mode !== 'silent') {
|
|
87
|
-
const message = this.formatMessage(...args);
|
|
88
|
-
console.log(this.useColor ? chalk.bold(message) : message);
|
|
89
|
-
this.log(message);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Success message
|
|
95
|
-
*/
|
|
96
|
-
success(...args) {
|
|
97
|
-
if (this.shouldOutput('normal')) {
|
|
98
|
-
const message = this.formatMessage('✅', ...args);
|
|
99
|
-
console.log(this.useColor ? chalk.green(message) : message);
|
|
100
|
-
this.log(message);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Warning message
|
|
106
|
-
*/
|
|
107
|
-
warn(...args) {
|
|
108
|
-
if (this.mode !== 'silent') {
|
|
109
|
-
const message = this.formatMessage('⚠️', ...args);
|
|
110
|
-
console.warn(this.useColor ? chalk.yellow(message) : message);
|
|
111
|
-
this.log(message);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Error message
|
|
117
|
-
*/
|
|
118
|
-
error(...args) {
|
|
119
|
-
const message = this.formatMessage('❌', ...args);
|
|
120
|
-
console.error(this.useColor ? chalk.red(message) : message);
|
|
121
|
-
this.log(message);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Format message with optional timestamp
|
|
126
|
-
* @private
|
|
127
|
-
*/
|
|
128
|
-
formatMessage(...args) {
|
|
129
|
-
let message = args.join(' ');
|
|
130
|
-
|
|
131
|
-
if (this.timestamps) {
|
|
132
|
-
const timestamp = new Date().toISOString();
|
|
133
|
-
message = `[${timestamp}] ${message}`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return message;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Log to file if configured
|
|
141
|
-
* @private
|
|
142
|
-
*/
|
|
143
|
-
log(message) {
|
|
144
|
-
if (this.logFile) {
|
|
145
|
-
const fs = require('fs');
|
|
146
|
-
fs.appendFileSync(this.logFile, message + '\n');
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Output data in specified format
|
|
152
|
-
*/
|
|
153
|
-
output(data, options = {}) {
|
|
154
|
-
const format = options.format || this.format;
|
|
155
|
-
|
|
156
|
-
switch (format) {
|
|
157
|
-
case 'json':
|
|
158
|
-
this.outputJSON(data, options);
|
|
159
|
-
break;
|
|
160
|
-
case 'table':
|
|
161
|
-
this.outputTable(data, options);
|
|
162
|
-
break;
|
|
163
|
-
case 'csv':
|
|
164
|
-
this.outputCSV(data, options);
|
|
165
|
-
break;
|
|
166
|
-
default:
|
|
167
|
-
this.outputText(data, options);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Output as JSON
|
|
173
|
-
*/
|
|
174
|
-
outputJSON(data, options = {}) {
|
|
175
|
-
const pretty = options.pretty !== false;
|
|
176
|
-
const output = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
177
|
-
console.log(output);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Output as table
|
|
182
|
-
*/
|
|
183
|
-
outputTable(data, options = {}) {
|
|
184
|
-
if (!Array.isArray(data) || data.length === 0) {
|
|
185
|
-
console.log('No data to display');
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const headers = options.headers || Object.keys(data[0]);
|
|
190
|
-
const columnWidths = this.calculateColumnWidths(data, headers);
|
|
191
|
-
|
|
192
|
-
// Print header
|
|
193
|
-
this.printTableRow(headers, columnWidths, true);
|
|
194
|
-
this.printTableSeparator(columnWidths);
|
|
195
|
-
|
|
196
|
-
// Print data rows
|
|
197
|
-
for (const row of data) {
|
|
198
|
-
const values = headers.map(h => row[h]);
|
|
199
|
-
this.printTableRow(values, columnWidths);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Output as CSV
|
|
205
|
-
*/
|
|
206
|
-
outputCSV(data, options = {}) {
|
|
207
|
-
if (!Array.isArray(data) || data.length === 0) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const headers = options.headers || Object.keys(data[0]);
|
|
212
|
-
console.log(headers.join(','));
|
|
213
|
-
|
|
214
|
-
for (const row of data) {
|
|
215
|
-
const values = headers.map(h => {
|
|
216
|
-
const value = row[h];
|
|
217
|
-
// Escape values containing commas or quotes
|
|
218
|
-
if (typeof value === 'string' && (value.includes(',') || value.includes('"'))) {
|
|
219
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
220
|
-
}
|
|
221
|
-
return value;
|
|
222
|
-
});
|
|
223
|
-
console.log(values.join(','));
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Output as text
|
|
229
|
-
*/
|
|
230
|
-
outputText(data, options = {}) {
|
|
231
|
-
if (typeof data === 'string') {
|
|
232
|
-
console.log(data);
|
|
233
|
-
} else if (Array.isArray(data)) {
|
|
234
|
-
data.forEach(item => console.log(item));
|
|
235
|
-
} else {
|
|
236
|
-
console.log(JSON.stringify(data, null, 2));
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Calculate column widths for table
|
|
242
|
-
* @private
|
|
243
|
-
*/
|
|
244
|
-
calculateColumnWidths(data, headers) {
|
|
245
|
-
const widths = {};
|
|
246
|
-
|
|
247
|
-
for (const header of headers) {
|
|
248
|
-
widths[header] = header.length;
|
|
249
|
-
|
|
250
|
-
for (const row of data) {
|
|
251
|
-
const value = String(row[header] || '');
|
|
252
|
-
widths[header] = Math.max(widths[header], value.length);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return widths;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Print table row
|
|
261
|
-
* @private
|
|
262
|
-
*/
|
|
263
|
-
printTableRow(values, widths, isHeader = false) {
|
|
264
|
-
const cells = values.map((value, index) => {
|
|
265
|
-
const width = widths[Object.keys(widths)[index]];
|
|
266
|
-
const str = String(value || '').padEnd(width);
|
|
267
|
-
return isHeader && this.useColor ? chalk.bold(str) : str;
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
console.log('| ' + cells.join(' | ') + ' |');
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Print table separator
|
|
275
|
-
* @private
|
|
276
|
-
*/
|
|
277
|
-
printTableSeparator(widths) {
|
|
278
|
-
const separators = Object.values(widths).map(w => '-'.repeat(w));
|
|
279
|
-
console.log('|-' + separators.join('-|-') + '-|');
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Create section header
|
|
284
|
-
*/
|
|
285
|
-
section(title) {
|
|
286
|
-
if (this.shouldOutput('normal')) {
|
|
287
|
-
const line = '='.repeat(title.length + 4);
|
|
288
|
-
console.log('\n' + (this.useColor ? chalk.cyan(line) : line));
|
|
289
|
-
console.log((this.useColor ? chalk.cyan.bold(` ${title} `) : ` ${title} `));
|
|
290
|
-
console.log((this.useColor ? chalk.cyan(line) : line) + '\n');
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Create subsection header
|
|
296
|
-
*/
|
|
297
|
-
subsection(title) {
|
|
298
|
-
if (this.shouldOutput('normal')) {
|
|
299
|
-
console.log('\n' + (this.useColor ? chalk.blue.bold(title) : title));
|
|
300
|
-
console.log((this.useColor ? chalk.blue('-'.repeat(title.length)) : '-'.repeat(title.length)));
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Print list items
|
|
306
|
-
*/
|
|
307
|
-
list(items, options = {}) {
|
|
308
|
-
if (!this.shouldOutput('normal')) return;
|
|
309
|
-
|
|
310
|
-
const bullet = options.bullet || '•';
|
|
311
|
-
const indent = options.indent || 2;
|
|
312
|
-
|
|
313
|
-
for (const item of items) {
|
|
314
|
-
const padding = ' '.repeat(indent);
|
|
315
|
-
console.log(`${padding}${bullet} ${item}`);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Print key-value pairs
|
|
321
|
-
*/
|
|
322
|
-
keyValue(pairs, options = {}) {
|
|
323
|
-
if (!this.shouldOutput('normal')) return;
|
|
324
|
-
|
|
325
|
-
const maxKeyLength = Math.max(...Object.keys(pairs).map(k => k.length));
|
|
326
|
-
|
|
327
|
-
for (const [key, value] of Object.entries(pairs)) {
|
|
328
|
-
const paddedKey = key.padEnd(maxKeyLength);
|
|
329
|
-
const formattedKey = this.useColor ? chalk.gray(paddedKey + ':') : paddedKey + ':';
|
|
330
|
-
console.log(` ${formattedKey} ${value}`);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
module.exports = OutputManager;
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Progress Indicator Helper
|
|
4
|
-
* Provides various progress indicators for long operations
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const readline = require('readline');
|
|
8
|
-
|
|
9
|
-
class ProgressIndicator {
|
|
10
|
-
constructor(options = {}) {
|
|
11
|
-
this.total = options.total || 0;
|
|
12
|
-
this.current = 0;
|
|
13
|
-
this.startTime = Date.now();
|
|
14
|
-
this.lastUpdate = 0;
|
|
15
|
-
this.updateInterval = options.updateInterval || 100; // ms
|
|
16
|
-
this.width = options.width || 40;
|
|
17
|
-
this.showETA = options.showETA !== false;
|
|
18
|
-
this.showSpeed = options.showSpeed !== false;
|
|
19
|
-
this.style = options.style || 'bar'; // bar, dots, spinner
|
|
20
|
-
this.message = options.message || 'Processing';
|
|
21
|
-
this.spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
22
|
-
this.spinnerIndex = 0;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Start progress tracking
|
|
27
|
-
*/
|
|
28
|
-
start(message) {
|
|
29
|
-
if (message) this.message = message;
|
|
30
|
-
this.startTime = Date.now();
|
|
31
|
-
this.current = 0;
|
|
32
|
-
this.lastUpdate = 0;
|
|
33
|
-
|
|
34
|
-
if (this.style === 'spinner') {
|
|
35
|
-
this.startSpinner();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Update progress
|
|
41
|
-
*/
|
|
42
|
-
update(current, message) {
|
|
43
|
-
if (current !== undefined) this.current = current;
|
|
44
|
-
if (message) this.message = message;
|
|
45
|
-
|
|
46
|
-
const now = Date.now();
|
|
47
|
-
if (now - this.lastUpdate < this.updateInterval) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
this.lastUpdate = now;
|
|
52
|
-
|
|
53
|
-
switch (this.style) {
|
|
54
|
-
case 'bar':
|
|
55
|
-
this.renderBar();
|
|
56
|
-
break;
|
|
57
|
-
case 'dots':
|
|
58
|
-
this.renderDots();
|
|
59
|
-
break;
|
|
60
|
-
case 'spinner':
|
|
61
|
-
this.renderSpinner();
|
|
62
|
-
break;
|
|
63
|
-
case 'percentage':
|
|
64
|
-
this.renderPercentage();
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Increment progress by 1
|
|
71
|
-
*/
|
|
72
|
-
increment(message) {
|
|
73
|
-
this.current++;
|
|
74
|
-
this.update(this.current, message);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Complete progress
|
|
79
|
-
*/
|
|
80
|
-
complete(message) {
|
|
81
|
-
this.current = this.total;
|
|
82
|
-
this.update(this.current, message || 'Complete');
|
|
83
|
-
process.stdout.write('\n');
|
|
84
|
-
|
|
85
|
-
if (this.spinnerInterval) {
|
|
86
|
-
clearInterval(this.spinnerInterval);
|
|
87
|
-
this.spinnerInterval = null;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Render progress bar
|
|
93
|
-
* @private
|
|
94
|
-
*/
|
|
95
|
-
renderBar() {
|
|
96
|
-
const percent = this.total > 0 ? this.current / this.total : 0;
|
|
97
|
-
const filled = Math.round(percent * this.width);
|
|
98
|
-
const empty = this.width - filled;
|
|
99
|
-
|
|
100
|
-
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
101
|
-
const percentStr = `${Math.round(percent * 100)}%`;
|
|
102
|
-
|
|
103
|
-
let output = `\r${this.message}: ${bar} ${percentStr}`;
|
|
104
|
-
|
|
105
|
-
if (this.total > 0) {
|
|
106
|
-
output += ` (${this.current}/${this.total})`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (this.showETA && this.total > 0 && this.current > 0) {
|
|
110
|
-
const eta = this.calculateETA();
|
|
111
|
-
output += ` ETA: ${eta}`;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (this.showSpeed && this.current > 0) {
|
|
115
|
-
const speed = this.calculateSpeed();
|
|
116
|
-
output += ` ${speed}/s`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
process.stdout.write(output);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Render dots progress
|
|
124
|
-
* @private
|
|
125
|
-
*/
|
|
126
|
-
renderDots() {
|
|
127
|
-
const dots = '.'.repeat((this.current % 4) + 1).padEnd(4);
|
|
128
|
-
const percent = this.total > 0 ? Math.round((this.current / this.total) * 100) : 0;
|
|
129
|
-
|
|
130
|
-
let output = `\r${this.message}${dots}`;
|
|
131
|
-
|
|
132
|
-
if (this.total > 0) {
|
|
133
|
-
output += ` ${percent}% (${this.current}/${this.total})`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
process.stdout.write(output.padEnd(80));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Render spinner
|
|
141
|
-
* @private
|
|
142
|
-
*/
|
|
143
|
-
renderSpinner() {
|
|
144
|
-
const frame = this.spinnerFrames[this.spinnerIndex];
|
|
145
|
-
let output = `\r${frame} ${this.message}`;
|
|
146
|
-
|
|
147
|
-
if (this.total > 0 && this.current > 0) {
|
|
148
|
-
const percent = Math.round((this.current / this.total) * 100);
|
|
149
|
-
output += ` ${percent}% (${this.current}/${this.total})`;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
process.stdout.write(output.padEnd(80));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Render percentage only
|
|
157
|
-
* @private
|
|
158
|
-
*/
|
|
159
|
-
renderPercentage() {
|
|
160
|
-
const percent = this.total > 0 ? Math.round((this.current / this.total) * 100) : 0;
|
|
161
|
-
const output = `\r${this.message}: ${percent}% (${this.current}/${this.total})`;
|
|
162
|
-
process.stdout.write(output.padEnd(80));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Start spinner animation
|
|
167
|
-
* @private
|
|
168
|
-
*/
|
|
169
|
-
startSpinner() {
|
|
170
|
-
this.spinnerInterval = setInterval(() => {
|
|
171
|
-
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
|
|
172
|
-
this.renderSpinner();
|
|
173
|
-
}, 80);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Calculate ETA
|
|
178
|
-
* @private
|
|
179
|
-
*/
|
|
180
|
-
calculateETA() {
|
|
181
|
-
const elapsed = Date.now() - this.startTime;
|
|
182
|
-
const rate = this.current / elapsed;
|
|
183
|
-
const remaining = this.total - this.current;
|
|
184
|
-
const eta = remaining / rate;
|
|
185
|
-
|
|
186
|
-
return this.formatTime(eta);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Calculate processing speed
|
|
191
|
-
* @private
|
|
192
|
-
*/
|
|
193
|
-
calculateSpeed() {
|
|
194
|
-
const elapsed = (Date.now() - this.startTime) / 1000; // seconds
|
|
195
|
-
const speed = this.current / elapsed;
|
|
196
|
-
|
|
197
|
-
if (speed > 1000) {
|
|
198
|
-
return `${(speed / 1000).toFixed(1)}k`;
|
|
199
|
-
}
|
|
200
|
-
return speed.toFixed(1);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Format time duration
|
|
205
|
-
* @private
|
|
206
|
-
*/
|
|
207
|
-
formatTime(ms) {
|
|
208
|
-
const seconds = Math.floor(ms / 1000);
|
|
209
|
-
const minutes = Math.floor(seconds / 60);
|
|
210
|
-
const hours = Math.floor(minutes / 60);
|
|
211
|
-
|
|
212
|
-
if (hours > 0) {
|
|
213
|
-
return `${hours}h ${minutes % 60}m`;
|
|
214
|
-
}
|
|
215
|
-
if (minutes > 0) {
|
|
216
|
-
return `${minutes}m ${seconds % 60}s`;
|
|
217
|
-
}
|
|
218
|
-
return `${seconds}s`;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Create a simple progress callback
|
|
223
|
-
*/
|
|
224
|
-
static create(total, message = 'Processing') {
|
|
225
|
-
const progress = new ProgressIndicator({ total, message });
|
|
226
|
-
progress.start();
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
update: (current, msg) => progress.update(current, msg),
|
|
230
|
-
increment: (msg) => progress.increment(msg),
|
|
231
|
-
complete: (msg) => progress.complete(msg)
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Wrap an async function with progress tracking
|
|
237
|
-
*/
|
|
238
|
-
static async withProgress(items, processor, message = 'Processing items') {
|
|
239
|
-
const progress = new ProgressIndicator({
|
|
240
|
-
total: items.length,
|
|
241
|
-
message,
|
|
242
|
-
style: 'bar'
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
progress.start();
|
|
246
|
-
|
|
247
|
-
const results = [];
|
|
248
|
-
for (let i = 0; i < items.length; i++) {
|
|
249
|
-
results.push(await processor(items[i], i));
|
|
250
|
-
progress.increment();
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
progress.complete();
|
|
254
|
-
return results;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
module.exports = ProgressIndicator;
|