agileflow 2.89.2 → 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.
Files changed (57) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +3 -3
  3. package/lib/content-sanitizer.js +463 -0
  4. package/lib/error-codes.js +544 -0
  5. package/lib/errors.js +336 -5
  6. package/lib/feedback.js +561 -0
  7. package/lib/path-resolver.js +396 -0
  8. package/lib/placeholder-registry.js +617 -0
  9. package/lib/session-registry.js +461 -0
  10. package/lib/smart-json-file.js +653 -0
  11. package/lib/table-formatter.js +504 -0
  12. package/lib/transient-status.js +374 -0
  13. package/lib/ui-manager.js +612 -0
  14. package/lib/validate-args.js +213 -0
  15. package/lib/validate-names.js +143 -0
  16. package/lib/validate-paths.js +434 -0
  17. package/lib/validate.js +38 -584
  18. package/package.json +4 -1
  19. package/scripts/agileflow-configure.js +40 -1440
  20. package/scripts/agileflow-welcome.js +2 -1
  21. package/scripts/check-update.js +16 -3
  22. package/scripts/lib/configure-detect.js +383 -0
  23. package/scripts/lib/configure-features.js +811 -0
  24. package/scripts/lib/configure-repair.js +314 -0
  25. package/scripts/lib/configure-utils.js +115 -0
  26. package/scripts/lib/frontmatter-parser.js +3 -3
  27. package/scripts/lib/sessionRegistry.js +682 -0
  28. package/scripts/obtain-context.js +417 -113
  29. package/scripts/ralph-loop.js +1 -1
  30. package/scripts/session-manager.js +77 -10
  31. package/scripts/tui/App.js +176 -0
  32. package/scripts/tui/index.js +75 -0
  33. package/scripts/tui/lib/crashRecovery.js +302 -0
  34. package/scripts/tui/lib/eventStream.js +316 -0
  35. package/scripts/tui/lib/keyboard.js +252 -0
  36. package/scripts/tui/lib/loopControl.js +371 -0
  37. package/scripts/tui/panels/OutputPanel.js +278 -0
  38. package/scripts/tui/panels/SessionPanel.js +178 -0
  39. package/scripts/tui/panels/TracePanel.js +333 -0
  40. package/src/core/commands/tui.md +91 -0
  41. package/tools/cli/commands/config.js +10 -33
  42. package/tools/cli/commands/doctor.js +48 -40
  43. package/tools/cli/commands/list.js +49 -37
  44. package/tools/cli/commands/status.js +13 -37
  45. package/tools/cli/commands/uninstall.js +12 -41
  46. package/tools/cli/installers/core/installer.js +75 -12
  47. package/tools/cli/installers/ide/_interface.js +238 -0
  48. package/tools/cli/installers/ide/codex.js +2 -2
  49. package/tools/cli/installers/ide/manager.js +15 -0
  50. package/tools/cli/lib/command-context.js +374 -0
  51. package/tools/cli/lib/config-manager.js +394 -0
  52. package/tools/cli/lib/content-injector.js +69 -16
  53. package/tools/cli/lib/ide-errors.js +163 -29
  54. package/tools/cli/lib/ide-registry.js +186 -0
  55. package/tools/cli/lib/npm-utils.js +16 -3
  56. package/tools/cli/lib/self-update.js +148 -0
  57. 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
+ };