agileflow 2.90.0 → 2.90.2
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 +10 -0
- package/README.md +3 -3
- package/lib/smart-json-file.js +33 -10
- package/lib/table-formatter.js +28 -13
- package/package.json +3 -4
- package/scripts/check-update.js +2 -1
- package/scripts/lib/sessionRegistry.js +4 -8
- package/scripts/tui/App.js +33 -58
- package/scripts/tui/index.js +2 -46
- package/scripts/tui/lib/crashRecovery.js +16 -14
- package/scripts/tui/lib/eventStream.js +8 -15
- package/scripts/tui/lib/keyboard.js +16 -7
- package/scripts/tui/lib/loopControl.js +9 -9
- package/scripts/tui/panels/OutputPanel.js +25 -61
- package/scripts/tui/panels/SessionPanel.js +4 -12
- package/scripts/tui/panels/TracePanel.js +27 -62
- package/scripts/tui/simple-tui.js +420 -0
- package/tools/cli/commands/config.js +0 -1
- package/tools/cli/commands/doctor.js +18 -9
- package/tools/cli/commands/status.js +14 -8
- package/tools/cli/commands/tui.js +59 -0
- package/tools/cli/commands/uninstall.js +4 -2
- package/tools/cli/lib/command-context.js +9 -1
- package/tools/cli/lib/npm-utils.js +2 -1
- package/src/core/commands/tui.md +0 -91
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AgileFlow Simple TUI - Terminal User Interface
|
|
6
|
+
*
|
|
7
|
+
* A simple terminal-based dashboard that works without React/ink dependencies.
|
|
8
|
+
* Uses ANSI escape codes for styling and keyboard input.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node scripts/tui/simple-tui.js
|
|
12
|
+
* npx agileflow tui
|
|
13
|
+
*
|
|
14
|
+
* Key bindings:
|
|
15
|
+
* q - Quit TUI
|
|
16
|
+
* s - Start loop on current story
|
|
17
|
+
* p - Pause active loop
|
|
18
|
+
* r - Resume paused loop
|
|
19
|
+
* t - Toggle trace panel
|
|
20
|
+
* 1-9 - Switch session focus
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const readline = require('readline');
|
|
26
|
+
|
|
27
|
+
// ANSI escape codes
|
|
28
|
+
const ANSI = {
|
|
29
|
+
clear: '\x1b[2J',
|
|
30
|
+
home: '\x1b[H',
|
|
31
|
+
reset: '\x1b[0m',
|
|
32
|
+
bold: '\x1b[1m',
|
|
33
|
+
dim: '\x1b[2m',
|
|
34
|
+
italic: '\x1b[3m',
|
|
35
|
+
underline: '\x1b[4m',
|
|
36
|
+
// Colors
|
|
37
|
+
black: '\x1b[30m',
|
|
38
|
+
red: '\x1b[31m',
|
|
39
|
+
green: '\x1b[32m',
|
|
40
|
+
yellow: '\x1b[33m',
|
|
41
|
+
blue: '\x1b[34m',
|
|
42
|
+
magenta: '\x1b[35m',
|
|
43
|
+
cyan: '\x1b[36m',
|
|
44
|
+
white: '\x1b[37m',
|
|
45
|
+
gray: '\x1b[90m',
|
|
46
|
+
// Backgrounds
|
|
47
|
+
bgBlack: '\x1b[40m',
|
|
48
|
+
bgRed: '\x1b[41m',
|
|
49
|
+
bgGreen: '\x1b[42m',
|
|
50
|
+
bgYellow: '\x1b[43m',
|
|
51
|
+
bgBlue: '\x1b[44m',
|
|
52
|
+
bgCyan: '\x1b[46m',
|
|
53
|
+
// Cursor
|
|
54
|
+
hideCursor: '\x1b[?25l',
|
|
55
|
+
showCursor: '\x1b[?25h',
|
|
56
|
+
saveCursor: '\x1b[s',
|
|
57
|
+
restoreCursor: '\x1b[u',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Get project root
|
|
61
|
+
function getProjectRoot() {
|
|
62
|
+
return process.cwd();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Get sessions from registry
|
|
66
|
+
function getSessions() {
|
|
67
|
+
try {
|
|
68
|
+
const registryPath = path.join(getProjectRoot(), '.agileflow', 'sessions', 'registry.json');
|
|
69
|
+
if (!fs.existsSync(registryPath)) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const data = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
73
|
+
const sessions = data.sessions || {};
|
|
74
|
+
// Convert object to array (registry stores sessions as object with ID keys)
|
|
75
|
+
if (Array.isArray(sessions)) {
|
|
76
|
+
return sessions;
|
|
77
|
+
}
|
|
78
|
+
return Object.entries(sessions).map(([id, session]) => ({
|
|
79
|
+
id,
|
|
80
|
+
...session,
|
|
81
|
+
}));
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Get loop status
|
|
88
|
+
function getLoopStatus() {
|
|
89
|
+
try {
|
|
90
|
+
const statePath = path.join(getProjectRoot(), 'docs', '09-agents', 'session-state.json');
|
|
91
|
+
if (!fs.existsSync(statePath)) {
|
|
92
|
+
return { active: false };
|
|
93
|
+
}
|
|
94
|
+
const data = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
95
|
+
const loop = data.ralph_loop;
|
|
96
|
+
if (!loop || !loop.enabled) {
|
|
97
|
+
return { active: false };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
active: true,
|
|
101
|
+
paused: loop.paused || false,
|
|
102
|
+
epic: loop.epic,
|
|
103
|
+
currentStory: loop.current_story,
|
|
104
|
+
iteration: loop.iteration || 0,
|
|
105
|
+
maxIterations: loop.max_iterations || 20,
|
|
106
|
+
};
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return { active: false };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Get recent agent events
|
|
113
|
+
function getAgentEvents(limit = 10) {
|
|
114
|
+
try {
|
|
115
|
+
const logPath = path.join(getProjectRoot(), 'docs', '09-agents', 'bus', 'log.jsonl');
|
|
116
|
+
if (!fs.existsSync(logPath)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const content = fs.readFileSync(logPath, 'utf8');
|
|
120
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
121
|
+
const events = [];
|
|
122
|
+
for (const line of lines.slice(-limit)) {
|
|
123
|
+
try {
|
|
124
|
+
events.push(JSON.parse(line));
|
|
125
|
+
} catch (e) {
|
|
126
|
+
// Skip invalid JSON
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return events;
|
|
130
|
+
} catch (e) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Draw box with border
|
|
136
|
+
function drawBox(x, y, width, height, title = '', color = 'cyan') {
|
|
137
|
+
const colorCode = ANSI[color] || ANSI.cyan;
|
|
138
|
+
const lines = [];
|
|
139
|
+
|
|
140
|
+
// Top border
|
|
141
|
+
lines.push(`${colorCode}+${'─'.repeat(width - 2)}+${ANSI.reset}`);
|
|
142
|
+
|
|
143
|
+
// Title if provided
|
|
144
|
+
if (title) {
|
|
145
|
+
const titleStr = ` ${title} `;
|
|
146
|
+
const paddingLeft = Math.floor((width - 2 - titleStr.length) / 2);
|
|
147
|
+
const paddingRight = width - 2 - titleStr.length - paddingLeft;
|
|
148
|
+
lines[0] = `${colorCode}+${'─'.repeat(paddingLeft)}${ANSI.bold}${titleStr}${ANSI.reset}${colorCode}${'─'.repeat(paddingRight)}+${ANSI.reset}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Content lines
|
|
152
|
+
for (let i = 0; i < height - 2; i++) {
|
|
153
|
+
lines.push(`${colorCode}│${ANSI.reset}${' '.repeat(width - 2)}${colorCode}│${ANSI.reset}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Bottom border
|
|
157
|
+
lines.push(`${colorCode}+${'─'.repeat(width - 2)}+${ANSI.reset}`);
|
|
158
|
+
|
|
159
|
+
return lines;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Pad string to width
|
|
163
|
+
function pad(str, width, align = 'left') {
|
|
164
|
+
const s = String(str).slice(0, width);
|
|
165
|
+
const padding = width - s.length;
|
|
166
|
+
if (align === 'right') return ' '.repeat(padding) + s;
|
|
167
|
+
if (align === 'center')
|
|
168
|
+
return ' '.repeat(Math.floor(padding / 2)) + s + ' '.repeat(Math.ceil(padding / 2));
|
|
169
|
+
return s + ' '.repeat(padding);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Progress bar
|
|
173
|
+
function progressBar(value, max, width = 20) {
|
|
174
|
+
const percent = Math.min(100, Math.max(0, (value / max) * 100));
|
|
175
|
+
const filled = Math.round((percent / 100) * width);
|
|
176
|
+
const empty = width - filled;
|
|
177
|
+
|
|
178
|
+
let color = ANSI.red;
|
|
179
|
+
if (percent >= 80) color = ANSI.green;
|
|
180
|
+
else if (percent >= 50) color = ANSI.yellow;
|
|
181
|
+
|
|
182
|
+
return `${color}${'█'.repeat(filled)}${ANSI.dim}${'░'.repeat(empty)}${ANSI.reset} ${percent.toFixed(0)}%`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Main TUI class
|
|
186
|
+
class SimpleTUI {
|
|
187
|
+
constructor() {
|
|
188
|
+
this.running = false;
|
|
189
|
+
this.showTrace = true;
|
|
190
|
+
this.lastUpdate = new Date();
|
|
191
|
+
this.messages = [];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
start() {
|
|
195
|
+
this.running = true;
|
|
196
|
+
|
|
197
|
+
// Set up terminal
|
|
198
|
+
process.stdout.write(ANSI.clear + ANSI.home + ANSI.hideCursor);
|
|
199
|
+
|
|
200
|
+
// Enable raw mode for keyboard input
|
|
201
|
+
if (process.stdin.isTTY) {
|
|
202
|
+
readline.emitKeypressEvents(process.stdin);
|
|
203
|
+
process.stdin.setRawMode(true);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Handle keyboard input
|
|
207
|
+
process.stdin.on('keypress', (str, key) => {
|
|
208
|
+
this.handleKey(key);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Handle terminal resize
|
|
212
|
+
process.stdout.on('resize', () => {
|
|
213
|
+
this.render();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Initial render
|
|
217
|
+
this.render();
|
|
218
|
+
|
|
219
|
+
// Update loop
|
|
220
|
+
this.updateInterval = setInterval(() => {
|
|
221
|
+
this.render();
|
|
222
|
+
}, 2000);
|
|
223
|
+
|
|
224
|
+
this.addMessage('TUI', 'Dashboard started');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
stop() {
|
|
228
|
+
this.running = false;
|
|
229
|
+
|
|
230
|
+
if (this.updateInterval) {
|
|
231
|
+
clearInterval(this.updateInterval);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Restore terminal
|
|
235
|
+
process.stdout.write(ANSI.showCursor + ANSI.clear + ANSI.home);
|
|
236
|
+
|
|
237
|
+
if (process.stdin.isTTY) {
|
|
238
|
+
process.stdin.setRawMode(false);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log('AgileFlow TUI closed.');
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
handleKey(key) {
|
|
246
|
+
if (!key) return;
|
|
247
|
+
|
|
248
|
+
const k = key.name || key.sequence;
|
|
249
|
+
|
|
250
|
+
switch (k) {
|
|
251
|
+
case 'q':
|
|
252
|
+
case 'escape':
|
|
253
|
+
this.stop();
|
|
254
|
+
break;
|
|
255
|
+
case 't':
|
|
256
|
+
this.showTrace = !this.showTrace;
|
|
257
|
+
this.addMessage('TUI', `Trace panel ${this.showTrace ? 'shown' : 'hidden'}`);
|
|
258
|
+
this.render();
|
|
259
|
+
break;
|
|
260
|
+
case 's':
|
|
261
|
+
this.addMessage('TUI', 'Start loop requested (not implemented)');
|
|
262
|
+
this.render();
|
|
263
|
+
break;
|
|
264
|
+
case 'p':
|
|
265
|
+
this.addMessage('TUI', 'Pause requested (not implemented)');
|
|
266
|
+
this.render();
|
|
267
|
+
break;
|
|
268
|
+
case 'r':
|
|
269
|
+
this.addMessage('TUI', 'Resume requested (not implemented)');
|
|
270
|
+
this.render();
|
|
271
|
+
break;
|
|
272
|
+
case 'c':
|
|
273
|
+
if (key.ctrl) {
|
|
274
|
+
this.stop();
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
addMessage(agent, message) {
|
|
281
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
282
|
+
this.messages.push({ timestamp, agent, message });
|
|
283
|
+
if (this.messages.length > 50) {
|
|
284
|
+
this.messages.shift();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
render() {
|
|
289
|
+
const width = process.stdout.columns || 80;
|
|
290
|
+
const height = process.stdout.rows || 24;
|
|
291
|
+
const output = [];
|
|
292
|
+
|
|
293
|
+
// Clear screen
|
|
294
|
+
output.push(ANSI.clear + ANSI.home);
|
|
295
|
+
|
|
296
|
+
// Header
|
|
297
|
+
const title = ' AgileFlow TUI ';
|
|
298
|
+
const headerPadding = Math.floor((width - title.length) / 2);
|
|
299
|
+
output.push(
|
|
300
|
+
`${ANSI.bgCyan}${ANSI.black}${'═'.repeat(headerPadding)}${ANSI.bold}${title}${ANSI.reset}${ANSI.bgCyan}${ANSI.black}${'═'.repeat(width - headerPadding - title.length)}${ANSI.reset}`
|
|
301
|
+
);
|
|
302
|
+
output.push('');
|
|
303
|
+
|
|
304
|
+
// Calculate panel widths
|
|
305
|
+
const leftWidth = Math.floor(width * 0.4);
|
|
306
|
+
const rightWidth = width - leftWidth - 1;
|
|
307
|
+
const panelHeight = height - 6; // Leave room for header and footer
|
|
308
|
+
|
|
309
|
+
// Get data
|
|
310
|
+
const sessions = getSessions();
|
|
311
|
+
const loopStatus = getLoopStatus();
|
|
312
|
+
const agentEvents = getAgentEvents(8);
|
|
313
|
+
|
|
314
|
+
// Build left panel (sessions)
|
|
315
|
+
output.push(`${ANSI.cyan}${ANSI.bold}┌─ SESSIONS ─${'─'.repeat(leftWidth - 14)}┐${ANSI.reset}`);
|
|
316
|
+
|
|
317
|
+
if (sessions.length === 0) {
|
|
318
|
+
output.push(
|
|
319
|
+
`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}No active sessions${ANSI.reset}${' '.repeat(leftWidth - 21)}${ANSI.cyan}│${ANSI.reset}`
|
|
320
|
+
);
|
|
321
|
+
} else {
|
|
322
|
+
for (const session of sessions.slice(0, 5)) {
|
|
323
|
+
const indicator = session.current ? `${ANSI.green}>` : ' ';
|
|
324
|
+
const name = `Session ${session.id}${session.is_main ? ' [main]' : ''}`;
|
|
325
|
+
const branch = session.branch || 'unknown';
|
|
326
|
+
const story = session.story || 'none';
|
|
327
|
+
|
|
328
|
+
output.push(
|
|
329
|
+
`${ANSI.cyan}│${ANSI.reset} ${indicator} ${ANSI.bold}${pad(name, leftWidth - 6)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
|
|
330
|
+
);
|
|
331
|
+
output.push(
|
|
332
|
+
`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}Branch:${ANSI.reset} ${ANSI.cyan}${pad(branch, leftWidth - 12)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
|
|
333
|
+
);
|
|
334
|
+
output.push(
|
|
335
|
+
`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}Story:${ANSI.reset} ${ANSI.yellow}${pad(story, leftWidth - 12)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Fill remaining space in left panel
|
|
341
|
+
const usedRows = sessions.length === 0 ? 1 : Math.min(sessions.length, 5) * 3;
|
|
342
|
+
const remainingRows = Math.max(0, panelHeight - usedRows - 2);
|
|
343
|
+
for (let i = 0; i < remainingRows; i++) {
|
|
344
|
+
output.push(
|
|
345
|
+
`${ANSI.cyan}│${ANSI.reset}${' '.repeat(leftWidth - 2)}${ANSI.cyan}│${ANSI.reset}`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
output.push(`${ANSI.cyan}└${'─'.repeat(leftWidth - 2)}┘${ANSI.reset}`);
|
|
350
|
+
|
|
351
|
+
// Move cursor to right panel position and draw
|
|
352
|
+
// For simplicity, we'll draw the right panel below the left panel
|
|
353
|
+
output.push('');
|
|
354
|
+
output.push(
|
|
355
|
+
`${ANSI.green}${ANSI.bold}┌─ AGENT OUTPUT ─${'─'.repeat(width - 19)}┐${ANSI.reset}`
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
if (agentEvents.length === 0 && this.messages.length === 0) {
|
|
359
|
+
output.push(
|
|
360
|
+
`${ANSI.green}│${ANSI.reset} ${ANSI.dim}Waiting for agent activity...${ANSI.reset}${' '.repeat(width - 34)}${ANSI.green}│${ANSI.reset}`
|
|
361
|
+
);
|
|
362
|
+
} else {
|
|
363
|
+
// Show recent events
|
|
364
|
+
const allMessages = [
|
|
365
|
+
...agentEvents.map(e => ({
|
|
366
|
+
timestamp: e.timestamp ? new Date(e.timestamp).toLocaleTimeString() : '',
|
|
367
|
+
agent: e.agent || 'unknown',
|
|
368
|
+
message:
|
|
369
|
+
e.message ||
|
|
370
|
+
(e.event === 'iteration' ? `Iteration ${e.iter}` : e.event || JSON.stringify(e)),
|
|
371
|
+
})),
|
|
372
|
+
...this.messages,
|
|
373
|
+
].slice(-8);
|
|
374
|
+
|
|
375
|
+
for (const msg of allMessages) {
|
|
376
|
+
const line = `[${msg.timestamp}] [${ANSI.cyan}${msg.agent}${ANSI.reset}] ${msg.message}`;
|
|
377
|
+
const cleanLine = `[${msg.timestamp}] [${msg.agent}] ${msg.message}`;
|
|
378
|
+
const padding = width - cleanLine.length - 4;
|
|
379
|
+
output.push(
|
|
380
|
+
`${ANSI.green}│${ANSI.reset} ${line}${' '.repeat(Math.max(0, padding))}${ANSI.green}│${ANSI.reset}`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
output.push(`${ANSI.green}└${'─'.repeat(width - 2)}┘${ANSI.reset}`);
|
|
386
|
+
|
|
387
|
+
// Loop status (if active)
|
|
388
|
+
if (loopStatus.active) {
|
|
389
|
+
output.push('');
|
|
390
|
+
const statusIcon = loopStatus.paused
|
|
391
|
+
? `${ANSI.yellow}||${ANSI.reset}`
|
|
392
|
+
: `${ANSI.green}>${ANSI.reset}`;
|
|
393
|
+
output.push(
|
|
394
|
+
`${statusIcon} ${ANSI.bold}Loop:${ANSI.reset} ${loopStatus.epic || 'unknown'} | Story: ${loopStatus.currentStory || 'none'} | ${progressBar(loopStatus.iteration, loopStatus.maxIterations, 15)}`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Footer with key bindings
|
|
399
|
+
output.push('');
|
|
400
|
+
output.push(
|
|
401
|
+
`${ANSI.dim}[Q]uit [S]tart [P]ause [R]esume [T]race [1-9]Sessions ${ANSI.reset}${ANSI.cyan}AgileFlow v2.90.0${ANSI.reset}`
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// Output everything
|
|
405
|
+
process.stdout.write(output.join('\n'));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Main entry point
|
|
410
|
+
function main() {
|
|
411
|
+
const tui = new SimpleTUI();
|
|
412
|
+
tui.start();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Run if executed directly
|
|
416
|
+
if (require.main === module) {
|
|
417
|
+
main();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
module.exports = { SimpleTUI, main };
|
|
@@ -369,7 +369,6 @@ function compareVersions(a, b) {
|
|
|
369
369
|
return 0;
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
|
|
373
372
|
/**
|
|
374
373
|
* Count files in directory recursively
|
|
375
374
|
* @param {string} dirPath - Directory path
|
|
@@ -402,16 +401,26 @@ function printSummary(issues, warnings) {
|
|
|
402
401
|
if (issues === 0 && warnings === 0) {
|
|
403
402
|
console.log(chalk.green.bold('No issues found.\n'));
|
|
404
403
|
} else if (issues === 0) {
|
|
405
|
-
console.log(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
404
|
+
console.log(
|
|
405
|
+
formatKeyValue(
|
|
406
|
+
{
|
|
407
|
+
Warnings: chalk.yellow(warnings),
|
|
408
|
+
Issues: chalk.green('0'),
|
|
409
|
+
},
|
|
410
|
+
{ separator: ':', alignValues: false }
|
|
411
|
+
)
|
|
412
|
+
);
|
|
409
413
|
console.log();
|
|
410
414
|
} else {
|
|
411
|
-
console.log(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
+
console.log(
|
|
416
|
+
formatKeyValue(
|
|
417
|
+
{
|
|
418
|
+
Issues: chalk.red(issues),
|
|
419
|
+
Warnings: chalk.yellow(warnings),
|
|
420
|
+
},
|
|
421
|
+
{ separator: ':', alignValues: false }
|
|
422
|
+
)
|
|
423
|
+
);
|
|
415
424
|
console.log();
|
|
416
425
|
}
|
|
417
426
|
}
|
|
@@ -36,17 +36,24 @@ module.exports = {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// Show installation info using formatKeyValue
|
|
39
|
-
console.log(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
console.log(
|
|
40
|
+
formatKeyValue({
|
|
41
|
+
Location: status.path,
|
|
42
|
+
Version: status.version,
|
|
43
|
+
})
|
|
44
|
+
);
|
|
43
45
|
|
|
44
46
|
// Count installed items
|
|
45
47
|
const counts = await installer.countInstalledItems(status.path);
|
|
46
48
|
|
|
47
|
-
console.log(
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
console.log(
|
|
50
|
+
formatKeyValue(
|
|
51
|
+
{
|
|
52
|
+
'\nCore': chalk.green('✓ Installed'),
|
|
53
|
+
},
|
|
54
|
+
{ alignValues: false }
|
|
55
|
+
)
|
|
56
|
+
);
|
|
50
57
|
info(`${counts.agents} agents`);
|
|
51
58
|
info(`${counts.commands} commands`);
|
|
52
59
|
info(`${counts.skills} skills`);
|
|
@@ -92,4 +99,3 @@ module.exports = {
|
|
|
92
99
|
}
|
|
93
100
|
},
|
|
94
101
|
};
|
|
95
|
-
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgileFlow CLI - TUI Command
|
|
3
|
+
*
|
|
4
|
+
* Launches the Terminal User Interface for real-time session monitoring.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const { spawn } = require('node:child_process');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
name: 'tui',
|
|
14
|
+
description: 'Launch Terminal User Interface for session monitoring',
|
|
15
|
+
options: [['-d, --directory <path>', 'Project directory (default: current directory)']],
|
|
16
|
+
action: async options => {
|
|
17
|
+
try {
|
|
18
|
+
const directory = path.resolve(options.directory || '.');
|
|
19
|
+
|
|
20
|
+
// Check if AgileFlow is installed
|
|
21
|
+
const agileflowDir = path.join(directory, '.agileflow');
|
|
22
|
+
if (!(await fs.pathExists(agileflowDir))) {
|
|
23
|
+
console.error(chalk.red('Error:'), 'AgileFlow is not installed in this directory');
|
|
24
|
+
console.log(chalk.dim(`Run 'npx agileflow setup' first\n`));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Find the TUI script
|
|
29
|
+
const tuiScript = path.join(__dirname, '../../..', 'scripts', 'tui', 'index.js');
|
|
30
|
+
|
|
31
|
+
if (!(await fs.pathExists(tuiScript))) {
|
|
32
|
+
console.error(chalk.red('Error:'), 'TUI script not found');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Spawn the TUI process with inherited stdio for interactive mode
|
|
37
|
+
const child = spawn('node', [tuiScript], {
|
|
38
|
+
cwd: directory,
|
|
39
|
+
stdio: 'inherit',
|
|
40
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
child.on('error', err => {
|
|
44
|
+
console.error(chalk.red('Error launching TUI:'), err.message);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
child.on('exit', code => {
|
|
49
|
+
process.exit(code || 0);
|
|
50
|
+
});
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error(chalk.red('Error:'), err.message);
|
|
53
|
+
if (process.env.DEBUG) {
|
|
54
|
+
console.error(err.stack);
|
|
55
|
+
}
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -51,7 +51,10 @@ module.exports = {
|
|
|
51
51
|
|
|
52
52
|
// Confirm removal
|
|
53
53
|
if (!options.force) {
|
|
54
|
-
const proceed = await confirm(
|
|
54
|
+
const proceed = await confirm(
|
|
55
|
+
`Remove ${IdeRegistry.getDisplayName(ideName)} configuration?`,
|
|
56
|
+
false
|
|
57
|
+
);
|
|
55
58
|
if (!proceed) {
|
|
56
59
|
console.log(chalk.dim('\nCancelled\n'));
|
|
57
60
|
process.exit(0);
|
|
@@ -150,4 +153,3 @@ module.exports = {
|
|
|
150
153
|
}
|
|
151
154
|
},
|
|
152
155
|
};
|
|
153
|
-
|
|
@@ -193,7 +193,15 @@ async function executeMiddleware(middlewares, ctx) {
|
|
|
193
193
|
* @returns {Object} Commander.js compatible command object
|
|
194
194
|
*/
|
|
195
195
|
function createCommand(definition) {
|
|
196
|
-
const {
|
|
196
|
+
const {
|
|
197
|
+
name,
|
|
198
|
+
description,
|
|
199
|
+
options = [],
|
|
200
|
+
arguments: args = [],
|
|
201
|
+
middleware: mw = [],
|
|
202
|
+
validate,
|
|
203
|
+
action,
|
|
204
|
+
} = definition;
|
|
197
205
|
|
|
198
206
|
// Build middleware pipeline
|
|
199
207
|
const pipeline = [...mw];
|
|
@@ -80,7 +80,8 @@ async function getLatestVersion(packageName) {
|
|
|
80
80
|
suggestion: 'Check network connection. If error persists, try: npm cache clean --force',
|
|
81
81
|
};
|
|
82
82
|
if (err.code === 'CERT_HAS_EXPIRED' || err.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
|
83
|
-
errorInfo.suggestion =
|
|
83
|
+
errorInfo.suggestion =
|
|
84
|
+
'TLS certificate error - check system time or update CA certificates';
|
|
84
85
|
}
|
|
85
86
|
debugLog('Network error', errorInfo);
|
|
86
87
|
resolve(null);
|
package/src/core/commands/tui.md
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Launch Terminal User Interface for real-time session monitoring
|
|
3
|
-
argument-hint:
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# /agileflow:tui
|
|
7
|
-
|
|
8
|
-
Launch the AgileFlow Terminal User Interface for real-time session visualization.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Purpose
|
|
13
|
-
|
|
14
|
-
The TUI provides a unified dashboard for:
|
|
15
|
-
- **Session monitoring** - See all active sessions and their current stories
|
|
16
|
-
- **Agent visualization** - Watch multi-agent orchestration in real-time
|
|
17
|
-
- **Loop control** - Pause, resume, and manage autonomous loops
|
|
18
|
-
- **Event streaming** - Live feed of agent activity and test results
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## IMMEDIATE ACTIONS
|
|
23
|
-
|
|
24
|
-
Upon invocation, execute these steps:
|
|
25
|
-
|
|
26
|
-
### Step 1: Launch TUI
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
node packages/cli/scripts/tui/index.js
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Step 2: Display Key Bindings
|
|
33
|
-
|
|
34
|
-
The TUI shows key bindings in the footer:
|
|
35
|
-
- **Q** - Quit TUI
|
|
36
|
-
- **S** - Start loop on current story
|
|
37
|
-
- **P** - Pause active loop
|
|
38
|
-
- **R** - Resume paused loop
|
|
39
|
-
- **T** - Toggle trace panel
|
|
40
|
-
- **1-9** - Switch session focus
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Key Features
|
|
45
|
-
|
|
46
|
-
### Session Overview Panel
|
|
47
|
-
- Lists all active sessions with ID, branch, and current story
|
|
48
|
-
- Color coding by thread type (base=green, parallel=cyan)
|
|
49
|
-
- Shows loop progress and iteration count
|
|
50
|
-
|
|
51
|
-
### Agent Output Panel
|
|
52
|
-
- Real-time stream of agent messages
|
|
53
|
-
- Timestamps and agent identification
|
|
54
|
-
- Auto-scroll with history buffer
|
|
55
|
-
|
|
56
|
-
### Trace Panel (Toggle with T)
|
|
57
|
-
- Detailed view of agent loops and quality gates
|
|
58
|
-
- Progress bars for each gate (tests, coverage, lint, types)
|
|
59
|
-
- Pass/fail status indicators
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## Example Session
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
┌─────────────────────────────────────────────────────────────────────┐
|
|
67
|
-
│ AgileFlow TUI │
|
|
68
|
-
├─────────────────────────────┬───────────────────────────────────────┤
|
|
69
|
-
│ SESSIONS │ AGENT OUTPUT │
|
|
70
|
-
│ ───────── │ ──────────── │
|
|
71
|
-
│ ▶ Session 1 [main] │ [agileflow-api] Reading status.json │
|
|
72
|
-
│ Story: US-0115 │ [agileflow-api] Running tests... │
|
|
73
|
-
│ Loop: 3/20 iterations │ ✓ 47 tests passed │
|
|
74
|
-
├─────────────────────────────┴───────────────────────────────────────┤
|
|
75
|
-
│ [S]tart [P]ause [R]esume [T]race [Q]uit │
|
|
76
|
-
└─────────────────────────────────────────────────────────────────────┘
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## Related Commands
|
|
82
|
-
|
|
83
|
-
- `/agileflow:babysit MODE=loop` - Autonomous story processing
|
|
84
|
-
- `/agileflow:session:status` - Session information (non-TUI)
|
|
85
|
-
- `/agileflow:session:new` - Create parallel sessions
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## Research
|
|
90
|
-
|
|
91
|
-
Based on Ralph TUI research: `docs/10-research/20260114-ralph-loop-ralph-tui.md`
|