agileflow 2.89.3 → 2.90.0
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 +5 -0
- package/README.md +3 -3
- package/lib/placeholder-registry.js +617 -0
- package/lib/smart-json-file.js +205 -1
- package/lib/table-formatter.js +504 -0
- package/lib/transient-status.js +374 -0
- package/lib/ui-manager.js +612 -0
- package/lib/validate-args.js +213 -0
- package/lib/validate-names.js +143 -0
- package/lib/validate-paths.js +434 -0
- package/lib/validate.js +37 -737
- package/package.json +4 -1
- package/scripts/check-update.js +16 -3
- package/scripts/lib/sessionRegistry.js +682 -0
- package/scripts/session-manager.js +77 -10
- package/scripts/tui/App.js +176 -0
- package/scripts/tui/index.js +75 -0
- package/scripts/tui/lib/crashRecovery.js +302 -0
- package/scripts/tui/lib/eventStream.js +316 -0
- package/scripts/tui/lib/keyboard.js +252 -0
- package/scripts/tui/lib/loopControl.js +371 -0
- package/scripts/tui/panels/OutputPanel.js +278 -0
- package/scripts/tui/panels/SessionPanel.js +178 -0
- package/scripts/tui/panels/TracePanel.js +333 -0
- package/src/core/commands/tui.md +91 -0
- package/tools/cli/commands/config.js +7 -30
- package/tools/cli/commands/doctor.js +18 -38
- package/tools/cli/commands/list.js +47 -35
- package/tools/cli/commands/status.js +13 -37
- package/tools/cli/commands/uninstall.js +9 -38
- package/tools/cli/installers/core/installer.js +13 -0
- package/tools/cli/lib/command-context.js +374 -0
- package/tools/cli/lib/config-manager.js +394 -0
- package/tools/cli/lib/ide-registry.js +186 -0
- package/tools/cli/lib/npm-utils.js +16 -3
- package/tools/cli/lib/self-update.js +148 -0
- package/tools/cli/lib/validation-middleware.js +491 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* transient-status.js - TTY-Aware Transient Status Updates
|
|
3
|
+
*
|
|
4
|
+
* Provides in-place status updates that overwrite previous output
|
|
5
|
+
* using carriage returns. Automatically falls back to line-based
|
|
6
|
+
* output when not connected to a TTY.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - TTY detection for smart formatting
|
|
10
|
+
* - Carriage return updates (in-place overwrites)
|
|
11
|
+
* - Multi-line status support
|
|
12
|
+
* - Graceful degradation for non-TTY
|
|
13
|
+
* - Safe for piping to files or other processes
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const { TransientStatus } = require('./transient-status');
|
|
17
|
+
*
|
|
18
|
+
* const status = new TransientStatus();
|
|
19
|
+
* status.update('Processing file 1 of 10...');
|
|
20
|
+
* // ... do work ...
|
|
21
|
+
* status.update('Processing file 2 of 10...');
|
|
22
|
+
* // ... when done ...
|
|
23
|
+
* status.done('Processed 10 files');
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const { c } = require('./colors');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* TransientStatus - Single-line in-place status updates
|
|
30
|
+
*
|
|
31
|
+
* Updates overwrite the previous line when connected to a TTY.
|
|
32
|
+
* Falls back to printing each update on a new line otherwise.
|
|
33
|
+
*/
|
|
34
|
+
class TransientStatus {
|
|
35
|
+
/**
|
|
36
|
+
* @param {Object} [options={}] - Configuration options
|
|
37
|
+
* @param {boolean} [options.isTTY] - Override TTY detection
|
|
38
|
+
* @param {NodeJS.WriteStream} [options.stream=process.stdout] - Output stream
|
|
39
|
+
* @param {string} [options.prefix=''] - Prefix for all messages
|
|
40
|
+
* @param {boolean} [options.showTimestamp=false] - Include timestamp
|
|
41
|
+
*/
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
this.stream = options.stream || process.stdout;
|
|
44
|
+
this.isTTY = options.isTTY !== undefined ? options.isTTY : this.stream.isTTY === true;
|
|
45
|
+
this.prefix = options.prefix || '';
|
|
46
|
+
this.showTimestamp = options.showTimestamp || false;
|
|
47
|
+
this.lastLineLength = 0;
|
|
48
|
+
this.isActive = false;
|
|
49
|
+
this.startTime = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Format timestamp if enabled
|
|
54
|
+
* @returns {string}
|
|
55
|
+
* @private
|
|
56
|
+
*/
|
|
57
|
+
_formatTimestamp() {
|
|
58
|
+
if (!this.showTimestamp) return '';
|
|
59
|
+
const now = new Date();
|
|
60
|
+
const time = now.toTimeString().slice(0, 8);
|
|
61
|
+
return this.isTTY ? `${c.dim}[${time}]${c.reset} ` : `[${time}] `;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Clear the current line
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
68
|
+
_clearLine() {
|
|
69
|
+
if (this.isTTY && this.lastLineLength > 0) {
|
|
70
|
+
this.stream.clearLine(0);
|
|
71
|
+
this.stream.cursorTo(0);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Update the status message (overwrites previous in TTY mode)
|
|
77
|
+
*
|
|
78
|
+
* @param {string} message - Status message
|
|
79
|
+
* @returns {TransientStatus} this for chaining
|
|
80
|
+
*/
|
|
81
|
+
update(message) {
|
|
82
|
+
if (!this.isActive) {
|
|
83
|
+
this.isActive = true;
|
|
84
|
+
this.startTime = Date.now();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const timestamp = this._formatTimestamp();
|
|
88
|
+
const fullMessage = `${timestamp}${this.prefix}${message}`;
|
|
89
|
+
|
|
90
|
+
if (this.isTTY) {
|
|
91
|
+
this._clearLine();
|
|
92
|
+
this.stream.write(fullMessage);
|
|
93
|
+
this.lastLineLength = fullMessage.length;
|
|
94
|
+
} else {
|
|
95
|
+
// Non-TTY: print on new line each time
|
|
96
|
+
this.stream.write(fullMessage + '\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Update with a progress indicator
|
|
104
|
+
*
|
|
105
|
+
* @param {number} current - Current item
|
|
106
|
+
* @param {number} total - Total items
|
|
107
|
+
* @param {string} [message] - Optional message
|
|
108
|
+
* @returns {TransientStatus} this for chaining
|
|
109
|
+
*/
|
|
110
|
+
progress(current, total, message = '') {
|
|
111
|
+
const percent = total > 0 ? Math.round((current / total) * 100) : 0;
|
|
112
|
+
const progressText = `[${current}/${total}] ${percent}%`;
|
|
113
|
+
const fullMessage = message ? `${progressText} ${message}` : progressText;
|
|
114
|
+
|
|
115
|
+
return this.update(fullMessage);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Clear status and print a final success message
|
|
120
|
+
*
|
|
121
|
+
* @param {string} [message] - Final message
|
|
122
|
+
* @returns {TransientStatus} this for chaining
|
|
123
|
+
*/
|
|
124
|
+
done(message = '') {
|
|
125
|
+
this._clearLine();
|
|
126
|
+
|
|
127
|
+
if (message) {
|
|
128
|
+
const elapsed = this.startTime ? Date.now() - this.startTime : 0;
|
|
129
|
+
const elapsedStr = elapsed > 400 ? ` ${c.dim}(${formatMs(elapsed)})${c.reset}` : '';
|
|
130
|
+
|
|
131
|
+
if (this.isTTY) {
|
|
132
|
+
this.stream.write(`${c.green}✓${c.reset} ${message}${elapsedStr}\n`);
|
|
133
|
+
} else {
|
|
134
|
+
this.stream.write(`✓ ${message}\n`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.isActive = false;
|
|
139
|
+
this.lastLineLength = 0;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Clear status and print a final failure message
|
|
145
|
+
*
|
|
146
|
+
* @param {string} [message] - Final message
|
|
147
|
+
* @returns {TransientStatus} this for chaining
|
|
148
|
+
*/
|
|
149
|
+
fail(message = '') {
|
|
150
|
+
this._clearLine();
|
|
151
|
+
|
|
152
|
+
if (message) {
|
|
153
|
+
if (this.isTTY) {
|
|
154
|
+
this.stream.write(`${c.red}✗${c.reset} ${message}\n`);
|
|
155
|
+
} else {
|
|
156
|
+
this.stream.write(`✗ ${message}\n`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.isActive = false;
|
|
161
|
+
this.lastLineLength = 0;
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Clear status and print a final warning message
|
|
167
|
+
*
|
|
168
|
+
* @param {string} [message] - Final message
|
|
169
|
+
* @returns {TransientStatus} this for chaining
|
|
170
|
+
*/
|
|
171
|
+
warn(message = '') {
|
|
172
|
+
this._clearLine();
|
|
173
|
+
|
|
174
|
+
if (message) {
|
|
175
|
+
if (this.isTTY) {
|
|
176
|
+
this.stream.write(`${c.yellow}⚠${c.reset} ${message}\n`);
|
|
177
|
+
} else {
|
|
178
|
+
this.stream.write(`⚠ ${message}\n`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.isActive = false;
|
|
183
|
+
this.lastLineLength = 0;
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Clear the status without printing anything
|
|
189
|
+
*
|
|
190
|
+
* @returns {TransientStatus} this for chaining
|
|
191
|
+
*/
|
|
192
|
+
clear() {
|
|
193
|
+
this._clearLine();
|
|
194
|
+
if (this.isTTY) {
|
|
195
|
+
this.stream.write('\n');
|
|
196
|
+
}
|
|
197
|
+
this.isActive = false;
|
|
198
|
+
this.lastLineLength = 0;
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* MultiLineStatus - Multi-line status that can update individual lines
|
|
205
|
+
*
|
|
206
|
+
* Useful for showing progress of multiple concurrent operations.
|
|
207
|
+
* Falls back to sequential updates when not connected to a TTY.
|
|
208
|
+
*/
|
|
209
|
+
class MultiLineStatus {
|
|
210
|
+
/**
|
|
211
|
+
* @param {number} lineCount - Number of status lines
|
|
212
|
+
* @param {Object} [options={}] - Configuration options
|
|
213
|
+
* @param {boolean} [options.isTTY] - Override TTY detection
|
|
214
|
+
* @param {NodeJS.WriteStream} [options.stream=process.stdout] - Output stream
|
|
215
|
+
*/
|
|
216
|
+
constructor(lineCount, options = {}) {
|
|
217
|
+
this.lineCount = lineCount;
|
|
218
|
+
this.stream = options.stream || process.stdout;
|
|
219
|
+
this.isTTY = options.isTTY !== undefined ? options.isTTY : this.stream.isTTY === true;
|
|
220
|
+
this.lines = new Array(lineCount).fill('');
|
|
221
|
+
this.isActive = false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Start displaying the multi-line status
|
|
226
|
+
* @returns {MultiLineStatus} this for chaining
|
|
227
|
+
*/
|
|
228
|
+
start() {
|
|
229
|
+
if (this.isTTY) {
|
|
230
|
+
// Print empty lines to reserve space
|
|
231
|
+
for (let i = 0; i < this.lineCount; i++) {
|
|
232
|
+
this.stream.write('\n');
|
|
233
|
+
}
|
|
234
|
+
// Move cursor back up
|
|
235
|
+
this.stream.moveCursor(0, -this.lineCount);
|
|
236
|
+
}
|
|
237
|
+
this.isActive = true;
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Update a specific line
|
|
243
|
+
*
|
|
244
|
+
* @param {number} lineIndex - Line index (0-based)
|
|
245
|
+
* @param {string} message - Message for this line
|
|
246
|
+
* @returns {MultiLineStatus} this for chaining
|
|
247
|
+
*/
|
|
248
|
+
updateLine(lineIndex, message) {
|
|
249
|
+
if (lineIndex < 0 || lineIndex >= this.lineCount) return this;
|
|
250
|
+
|
|
251
|
+
this.lines[lineIndex] = message;
|
|
252
|
+
|
|
253
|
+
if (this.isTTY) {
|
|
254
|
+
// Save cursor position
|
|
255
|
+
const currentLine = lineIndex;
|
|
256
|
+
|
|
257
|
+
// Move to the correct line
|
|
258
|
+
if (currentLine > 0) {
|
|
259
|
+
this.stream.moveCursor(0, currentLine);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Clear and write
|
|
263
|
+
this.stream.clearLine(0);
|
|
264
|
+
this.stream.cursorTo(0);
|
|
265
|
+
this.stream.write(message);
|
|
266
|
+
|
|
267
|
+
// Move cursor back to start position
|
|
268
|
+
if (currentLine > 0) {
|
|
269
|
+
this.stream.moveCursor(0, -currentLine);
|
|
270
|
+
}
|
|
271
|
+
this.stream.cursorTo(0);
|
|
272
|
+
} else {
|
|
273
|
+
// Non-TTY: just print the update
|
|
274
|
+
this.stream.write(`[${lineIndex + 1}/${this.lineCount}] ${message}\n`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Finish and move cursor past all lines
|
|
282
|
+
* @returns {MultiLineStatus} this for chaining
|
|
283
|
+
*/
|
|
284
|
+
done() {
|
|
285
|
+
if (this.isTTY) {
|
|
286
|
+
// Move cursor to end
|
|
287
|
+
this.stream.moveCursor(0, this.lineCount);
|
|
288
|
+
this.stream.write('\n');
|
|
289
|
+
}
|
|
290
|
+
this.isActive = false;
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Clear all lines
|
|
296
|
+
* @returns {MultiLineStatus} this for chaining
|
|
297
|
+
*/
|
|
298
|
+
clear() {
|
|
299
|
+
if (this.isTTY) {
|
|
300
|
+
for (let i = 0; i < this.lineCount; i++) {
|
|
301
|
+
this.stream.clearLine(0);
|
|
302
|
+
if (i < this.lineCount - 1) {
|
|
303
|
+
this.stream.moveCursor(0, 1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Move back to start
|
|
307
|
+
this.stream.moveCursor(0, -(this.lineCount - 1));
|
|
308
|
+
this.stream.cursorTo(0);
|
|
309
|
+
}
|
|
310
|
+
this.lines.fill('');
|
|
311
|
+
this.isActive = false;
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Simple function to create and use a transient status in one go
|
|
318
|
+
*
|
|
319
|
+
* @param {string} initialMessage - Initial status message
|
|
320
|
+
* @param {Function} fn - Async function to run
|
|
321
|
+
* @param {Object} [options={}] - TransientStatus options
|
|
322
|
+
* @returns {Promise<any>} Result of the function
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* const result = await withStatus('Processing...', async (status) => {
|
|
326
|
+
* for (let i = 0; i < 10; i++) {
|
|
327
|
+
* status.update(`Processing item ${i + 1}...`);
|
|
328
|
+
* await doWork(i);
|
|
329
|
+
* }
|
|
330
|
+
* return 'done';
|
|
331
|
+
* });
|
|
332
|
+
*/
|
|
333
|
+
async function withStatus(initialMessage, fn, options = {}) {
|
|
334
|
+
const status = new TransientStatus(options);
|
|
335
|
+
status.update(initialMessage);
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const result = await fn(status);
|
|
339
|
+
return result;
|
|
340
|
+
} catch (error) {
|
|
341
|
+
status.fail(error.message);
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Format milliseconds as human-readable duration
|
|
348
|
+
* @param {number} ms - Milliseconds
|
|
349
|
+
* @returns {string} Formatted duration
|
|
350
|
+
*/
|
|
351
|
+
function formatMs(ms) {
|
|
352
|
+
if (ms < 1000) return `${ms}ms`;
|
|
353
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
354
|
+
const minutes = Math.floor(ms / 60000);
|
|
355
|
+
const seconds = Math.round((ms % 60000) / 1000);
|
|
356
|
+
return `${minutes}m ${seconds}s`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Check if output is going to a TTY
|
|
361
|
+
* @param {NodeJS.WriteStream} [stream=process.stdout] - Stream to check
|
|
362
|
+
* @returns {boolean}
|
|
363
|
+
*/
|
|
364
|
+
function isTTY(stream = process.stdout) {
|
|
365
|
+
return stream.isTTY === true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = {
|
|
369
|
+
TransientStatus,
|
|
370
|
+
MultiLineStatus,
|
|
371
|
+
withStatus,
|
|
372
|
+
formatMs,
|
|
373
|
+
isTTY,
|
|
374
|
+
};
|