agileflow 2.90.6 → 2.90.7
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/package.json +1 -1
- package/scripts/tui/index.js +2 -4
- package/scripts/tui/simple-tui.js +8 -5
- package/tools/cli/commands/tui.js +40 -271
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/scripts/tui/index.js
CHANGED
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
* 1-9 - Switch session focus
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
// The simple-tui provides responsive layouts and works reliably
|
|
23
|
+
// Use simple-tui (pure Node.js) - Ink has React version conflicts in monorepo
|
|
25
24
|
const useInk = false;
|
|
26
25
|
|
|
27
26
|
/**
|
|
@@ -46,8 +45,7 @@ async function main() {
|
|
|
46
45
|
// Wait for exit
|
|
47
46
|
await waitUntilExit();
|
|
48
47
|
} else {
|
|
49
|
-
//
|
|
50
|
-
console.log('React/Ink not available, using simple TUI...');
|
|
48
|
+
// Use simple TUI (pure Node.js, no React dependencies)
|
|
51
49
|
const { main: simpleTuiMain } = require('./simple-tui');
|
|
52
50
|
simpleTuiMain();
|
|
53
51
|
}
|
|
@@ -55,6 +55,7 @@ const ANSI = {
|
|
|
55
55
|
showCursor: '\x1b[?25h',
|
|
56
56
|
saveCursor: '\x1b[s',
|
|
57
57
|
restoreCursor: '\x1b[u',
|
|
58
|
+
clearLine: '\x1b[K', // Clear from cursor to end of line
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
// Get project root
|
|
@@ -213,7 +214,8 @@ class SimpleTUI {
|
|
|
213
214
|
this.render();
|
|
214
215
|
});
|
|
215
216
|
|
|
216
|
-
//
|
|
217
|
+
// Clear screen once at start, then render
|
|
218
|
+
process.stdout.write(ANSI.clear + ANSI.home);
|
|
217
219
|
this.render();
|
|
218
220
|
|
|
219
221
|
// Update loop
|
|
@@ -290,8 +292,8 @@ class SimpleTUI {
|
|
|
290
292
|
const height = process.stdout.rows || 24;
|
|
291
293
|
const output = [];
|
|
292
294
|
|
|
293
|
-
//
|
|
294
|
-
output.push(ANSI.
|
|
295
|
+
// Move to home position (don't clear - prevents bouncing)
|
|
296
|
+
output.push(ANSI.home);
|
|
295
297
|
|
|
296
298
|
// Determine layout mode based on terminal width
|
|
297
299
|
const isWide = width >= 100;
|
|
@@ -317,8 +319,9 @@ class SimpleTUI {
|
|
|
317
319
|
this.renderStacked(output, width, height, sessions, loopStatus, agentEvents, isNarrow);
|
|
318
320
|
}
|
|
319
321
|
|
|
320
|
-
// Output everything
|
|
321
|
-
|
|
322
|
+
// Output everything with line clearing to prevent artifacts
|
|
323
|
+
const outputWithClear = output.map(line => line + ANSI.clearLine);
|
|
324
|
+
process.stdout.write(outputWithClear.join('\n'));
|
|
322
325
|
}
|
|
323
326
|
|
|
324
327
|
renderSideBySide(output, width, height, sessions, loopStatus, agentEvents) {
|
|
@@ -1,287 +1,56 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* AgileFlow TUI
|
|
5
|
-
*
|
|
6
|
-
* Static dashboard view - prints once and exits.
|
|
7
|
-
* No continuous refresh, no screen clearing, scrollable output.
|
|
2
|
+
* AgileFlow CLI - TUI Command
|
|
8
3
|
*
|
|
9
|
-
*
|
|
4
|
+
* Launches the Terminal User Interface for real-time session monitoring.
|
|
5
|
+
* Uses React/Ink for a proper live-updating terminal UI.
|
|
10
6
|
*/
|
|
11
7
|
|
|
12
|
-
const path = require('path');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const { spawn } = require('node:child_process');
|
|
13
10
|
const fs = require('fs');
|
|
14
|
-
const { c: colors } = require('../../../lib/colors');
|
|
15
|
-
|
|
16
|
-
function showHeader(version) {
|
|
17
|
-
console.log('');
|
|
18
|
-
console.log(
|
|
19
|
-
`${colors.orange}${colors.bold} ╔═══════════════════════════════════════════╗${colors.reset}`
|
|
20
|
-
);
|
|
21
|
-
console.log(
|
|
22
|
-
`${colors.orange}${colors.bold} ║ AgileFlow Dashboard ║${colors.reset}`
|
|
23
|
-
);
|
|
24
|
-
console.log(
|
|
25
|
-
`${colors.orange}${colors.bold} ╚═══════════════════════════════════════════╝${colors.reset}`
|
|
26
|
-
);
|
|
27
|
-
if (version) {
|
|
28
|
-
console.log(`${colors.dim} v${version}${colors.reset}`);
|
|
29
|
-
}
|
|
30
|
-
console.log('');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function loadStatus() {
|
|
34
|
-
const statusPath = path.join(process.cwd(), 'docs', '09-agents', 'status.json');
|
|
35
|
-
|
|
36
|
-
if (!fs.existsSync(statusPath)) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const content = fs.readFileSync(statusPath, 'utf8');
|
|
42
|
-
return JSON.parse(content);
|
|
43
|
-
} catch (err) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function loadLoopStatus() {
|
|
49
|
-
const loopPath = path.join(process.cwd(), 'docs', '09-agents', 'loop-status.json');
|
|
50
|
-
|
|
51
|
-
if (!fs.existsSync(loopPath)) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const content = fs.readFileSync(loopPath, 'utf8');
|
|
57
|
-
return JSON.parse(content);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function getStatusColor(status) {
|
|
64
|
-
switch (status) {
|
|
65
|
-
case 'completed':
|
|
66
|
-
case 'done':
|
|
67
|
-
return colors.green;
|
|
68
|
-
case 'in_progress':
|
|
69
|
-
case 'in-progress':
|
|
70
|
-
return colors.yellow;
|
|
71
|
-
case 'blocked':
|
|
72
|
-
return colors.red;
|
|
73
|
-
case 'ready':
|
|
74
|
-
return colors.cyan;
|
|
75
|
-
default:
|
|
76
|
-
return colors.dim;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function getStatusSymbol(status) {
|
|
81
|
-
switch (status) {
|
|
82
|
-
case 'completed':
|
|
83
|
-
case 'done':
|
|
84
|
-
return '✓';
|
|
85
|
-
case 'in_progress':
|
|
86
|
-
case 'in-progress':
|
|
87
|
-
return '▶';
|
|
88
|
-
case 'blocked':
|
|
89
|
-
return '✗';
|
|
90
|
-
case 'ready':
|
|
91
|
-
return '○';
|
|
92
|
-
default:
|
|
93
|
-
return '·';
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function formatStory(story, compact = false) {
|
|
98
|
-
const statusColor = getStatusColor(story.status);
|
|
99
|
-
const symbol = getStatusSymbol(story.status);
|
|
100
|
-
const id = story.id || story.story_id || 'Unknown';
|
|
101
|
-
const title = story.title || story.summary || 'Untitled';
|
|
102
|
-
const status = (story.status || 'unknown').replace(/_/g, ' ');
|
|
103
|
-
const owner = story.owner || '-';
|
|
104
|
-
|
|
105
|
-
if (compact) {
|
|
106
|
-
const truncTitle = title.length > 35 ? title.substring(0, 32) + '...' : title;
|
|
107
|
-
return ` ${statusColor}${symbol}${colors.reset} ${colors.bold}${id}${colors.reset} ${truncTitle}`;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return ` ${statusColor}${symbol}${colors.reset} ${colors.bold}${id}${colors.reset} ${title.substring(0, 50)}${title.length > 50 ? '...' : ''}
|
|
111
|
-
${statusColor}${status}${colors.reset} ${colors.dim}│ Owner: ${owner}${colors.reset}`;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function drawProgressBar(percent, width = 20) {
|
|
115
|
-
const filled = Math.round((percent / 100) * width);
|
|
116
|
-
const empty = width - filled;
|
|
117
|
-
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
118
|
-
|
|
119
|
-
let color = colors.red;
|
|
120
|
-
if (percent >= 80) color = colors.green;
|
|
121
|
-
else if (percent >= 50) color = colors.yellow;
|
|
122
|
-
else if (percent >= 20) color = colors.cyan;
|
|
123
|
-
|
|
124
|
-
return `${color}${bar}${colors.reset} ${percent}%`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function showDashboard(options = {}) {
|
|
128
|
-
const version = options.version;
|
|
129
|
-
const compact = options.compact || false;
|
|
130
|
-
|
|
131
|
-
showHeader(version);
|
|
132
11
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
);
|
|
148
|
-
const counts = {
|
|
149
|
-
in_progress: stories.filter(s => ['in_progress', 'in-progress'].includes(s.status)).length,
|
|
150
|
-
blocked: stories.filter(s => s.status === 'blocked').length,
|
|
151
|
-
ready: stories.filter(s => s.status === 'ready').length,
|
|
152
|
-
completed: stories.filter(s => ['completed', 'done'].includes(s.status)).length,
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const total = stories.length;
|
|
156
|
-
const completionPct = total > 0 ? Math.round((counts.completed / total) * 100) : 0;
|
|
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
|
+
const directory = path.resolve(options.directory || '.');
|
|
18
|
+
|
|
19
|
+
// Check if AgileFlow is installed
|
|
20
|
+
const agileflowDir = path.join(directory, '.agileflow');
|
|
21
|
+
if (!fs.existsSync(agileflowDir)) {
|
|
22
|
+
console.error('Error: AgileFlow is not installed in this directory');
|
|
23
|
+
console.log("Run 'npx agileflow setup' first\n");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
157
26
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
console.log(
|
|
162
|
-
` ${colors.yellow}● In Progress:${colors.reset} ${counts.in_progress} ${colors.red}● Blocked:${colors.reset} ${counts.blocked} ${colors.cyan}● Ready:${colors.reset} ${counts.ready} ${colors.green}● Done:${colors.reset} ${counts.completed}`
|
|
163
|
-
);
|
|
164
|
-
console.log(` Progress: ${drawProgressBar(completionPct)}`);
|
|
165
|
-
console.log('');
|
|
27
|
+
// Find the TUI script - relative to this file in packages/cli
|
|
28
|
+
const cliRoot = path.join(__dirname, '..', '..', '..');
|
|
29
|
+
const tuiScript = path.join(cliRoot, 'scripts', 'tui', 'index.js');
|
|
166
30
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
loopStatus.state === 'running'
|
|
171
|
-
? colors.green
|
|
172
|
-
: loopStatus.state === 'paused'
|
|
173
|
-
? colors.yellow
|
|
174
|
-
: colors.dim;
|
|
175
|
-
console.log(`${colors.bold} ACTIVE LOOP${colors.reset}`);
|
|
176
|
-
console.log(` ${'─'.repeat(45)}`);
|
|
177
|
-
console.log(
|
|
178
|
-
` ${stateColor}●${colors.reset} ${loopStatus.epic || '-'} / ${loopStatus.story || '-'}`
|
|
179
|
-
);
|
|
180
|
-
if (loopStatus.progress !== undefined) {
|
|
181
|
-
console.log(` Progress: ${drawProgressBar(loopStatus.progress)}`);
|
|
31
|
+
if (!fs.existsSync(tuiScript)) {
|
|
32
|
+
console.error('Error: TUI script not found at', tuiScript);
|
|
33
|
+
process.exit(1);
|
|
182
34
|
}
|
|
183
|
-
console.log('');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// In Progress Stories
|
|
187
|
-
const inProgressStories = stories.filter(s => ['in_progress', 'in-progress'].includes(s.status));
|
|
188
|
-
if (inProgressStories.length > 0) {
|
|
189
|
-
console.log(`${colors.bold} ${colors.yellow}IN PROGRESS${colors.reset}`);
|
|
190
|
-
console.log(` ${'─'.repeat(45)}`);
|
|
191
|
-
inProgressStories.forEach(story => {
|
|
192
|
-
console.log(formatStory(story, compact));
|
|
193
|
-
});
|
|
194
|
-
console.log('');
|
|
195
|
-
}
|
|
196
35
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
36
|
+
// Spawn the TUI process from packages/cli directory to use correct deps
|
|
37
|
+
const child = spawn('node', [tuiScript], {
|
|
38
|
+
cwd: cliRoot, // Run from packages/cli to use its node_modules
|
|
39
|
+
stdio: 'inherit',
|
|
40
|
+
env: {
|
|
41
|
+
...process.env,
|
|
42
|
+
FORCE_COLOR: '1',
|
|
43
|
+
AGILEFLOW_PROJECT_DIR: directory, // Pass project dir as env var
|
|
44
|
+
},
|
|
204
45
|
});
|
|
205
|
-
console.log('');
|
|
206
|
-
}
|
|
207
46
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const moreReady =
|
|
212
|
-
counts.ready > 5 ? ` ${colors.dim}(+${counts.ready - 5} more)${colors.reset}` : '';
|
|
213
|
-
console.log(`${colors.bold} ${colors.cyan}READY${colors.reset}${moreReady}`);
|
|
214
|
-
console.log(` ${'─'.repeat(45)}`);
|
|
215
|
-
readyStories.forEach(story => {
|
|
216
|
-
console.log(formatStory(story, compact));
|
|
47
|
+
child.on('error', err => {
|
|
48
|
+
console.error('Error launching TUI:', err.message);
|
|
49
|
+
process.exit(1);
|
|
217
50
|
});
|
|
218
|
-
console.log('');
|
|
219
|
-
}
|
|
220
51
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
.filter(s => ['completed', 'done'].includes(s.status))
|
|
224
|
-
.slice(-3)
|
|
225
|
-
.reverse();
|
|
226
|
-
if (completedStories.length > 0) {
|
|
227
|
-
const moreCompleted =
|
|
228
|
-
counts.completed > 3 ? ` ${colors.dim}(+${counts.completed - 3} more)${colors.reset}` : '';
|
|
229
|
-
console.log(`${colors.bold} ${colors.green}RECENTLY COMPLETED${colors.reset}${moreCompleted}`);
|
|
230
|
-
console.log(` ${'─'.repeat(45)}`);
|
|
231
|
-
completedStories.forEach(story => {
|
|
232
|
-
console.log(formatStory(story, compact));
|
|
52
|
+
child.on('exit', code => {
|
|
53
|
+
process.exit(code || 0);
|
|
233
54
|
});
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Footer
|
|
238
|
-
console.log(`${colors.dim} ─────────────────────────────────────────────${colors.reset}`);
|
|
239
|
-
console.log(`${colors.dim} /agileflow:board Interactive kanban view${colors.reset}`);
|
|
240
|
-
console.log(`${colors.dim} /agileflow:story:list Full story listing${colors.reset}`);
|
|
241
|
-
console.log('');
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async function main(options = {}) {
|
|
245
|
-
const args = process.argv.slice(2);
|
|
246
|
-
|
|
247
|
-
// Check for help flag
|
|
248
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
249
|
-
console.log('');
|
|
250
|
-
console.log(`${colors.bold}AgileFlow TUI Dashboard${colors.reset}`);
|
|
251
|
-
console.log('');
|
|
252
|
-
console.log(`${colors.bold}Usage:${colors.reset}`);
|
|
253
|
-
console.log(' npx agileflow tui Show dashboard');
|
|
254
|
-
console.log(' npx agileflow tui --compact Compact view');
|
|
255
|
-
console.log(' npx agileflow tui --help Show this help');
|
|
256
|
-
console.log('');
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const compact = args.includes('--compact') || args.includes('-c');
|
|
261
|
-
|
|
262
|
-
// Get version from package.json
|
|
263
|
-
let version;
|
|
264
|
-
try {
|
|
265
|
-
const pkgPath = path.join(__dirname, '..', '..', '..', 'package.json');
|
|
266
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
267
|
-
version = pkg.version;
|
|
268
|
-
} catch (e) {
|
|
269
|
-
version = null;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
await showDashboard({ version, compact, ...options });
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Run directly if executed as script
|
|
276
|
-
if (require.main === module) {
|
|
277
|
-
main().catch(err => {
|
|
278
|
-
console.error(`${colors.red}Error:${colors.reset}`, err.message);
|
|
279
|
-
process.exit(1);
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
module.exports = {
|
|
284
|
-
name: 'tui',
|
|
285
|
-
description: 'Show project dashboard',
|
|
286
|
-
action: main,
|
|
55
|
+
},
|
|
287
56
|
};
|