agileflow 2.90.4 → 2.90.5
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 +1 -3
- package/scripts/tui/simple-tui.js +42 -14
- package/tools/cli/commands/start.js +290 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/scripts/tui/index.js
CHANGED
|
@@ -41,9 +41,7 @@ async function main() {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
// Render the dashboard
|
|
44
|
-
const { waitUntilExit } = render(
|
|
45
|
-
React.createElement(Dashboard, { onAction: handleAction })
|
|
46
|
-
);
|
|
44
|
+
const { waitUntilExit } = render(React.createElement(Dashboard, { onAction: handleAction }));
|
|
47
45
|
|
|
48
46
|
// Wait for exit
|
|
49
47
|
await waitUntilExit();
|
|
@@ -372,19 +372,27 @@ class SimpleTUI {
|
|
|
372
372
|
|
|
373
373
|
// Header
|
|
374
374
|
const header = `─ SESSIONS ─`;
|
|
375
|
-
lines.push(
|
|
375
|
+
lines.push(
|
|
376
|
+
`${ANSI.cyan}┌${header}${'─'.repeat(Math.max(0, innerWidth - header.length))}┐${ANSI.reset}`
|
|
377
|
+
);
|
|
376
378
|
|
|
377
379
|
if (sessions.length === 0) {
|
|
378
|
-
lines.push(
|
|
380
|
+
lines.push(
|
|
381
|
+
`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${pad('No active sessions', innerWidth)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
|
|
382
|
+
);
|
|
379
383
|
} else {
|
|
380
384
|
const maxSessions = Math.floor((panelHeight - 2) / 2);
|
|
381
385
|
for (const session of sessions.slice(0, maxSessions)) {
|
|
382
386
|
const indicator = session.current ? `${ANSI.green}▶` : ' ';
|
|
383
387
|
const name = `Session ${session.id}${session.is_main ? ' [main]' : ''}`;
|
|
384
|
-
lines.push(
|
|
388
|
+
lines.push(
|
|
389
|
+
`${ANSI.cyan}│${ANSI.reset}${indicator} ${ANSI.bold}${pad(name, innerWidth - 2)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
|
|
390
|
+
);
|
|
385
391
|
|
|
386
392
|
const info = `${session.branch || '?'}${session.story ? ' / ' + session.story : ''}`;
|
|
387
|
-
lines.push(
|
|
393
|
+
lines.push(
|
|
394
|
+
`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${pad(info, innerWidth - 1)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
|
|
395
|
+
);
|
|
388
396
|
}
|
|
389
397
|
}
|
|
390
398
|
|
|
@@ -405,12 +413,20 @@ class SimpleTUI {
|
|
|
405
413
|
|
|
406
414
|
// Header
|
|
407
415
|
const header = `─ OUTPUT ─`;
|
|
408
|
-
lines.push(
|
|
416
|
+
lines.push(
|
|
417
|
+
`${ANSI.green}┌${header}${'─'.repeat(Math.max(0, innerWidth - header.length))}┐${ANSI.reset}`
|
|
418
|
+
);
|
|
409
419
|
|
|
410
420
|
// Combine events and messages
|
|
411
421
|
const allMessages = [
|
|
412
422
|
...agentEvents.map(e => ({
|
|
413
|
-
time: e.timestamp
|
|
423
|
+
time: e.timestamp
|
|
424
|
+
? new Date(e.timestamp).toLocaleTimeString('en-US', {
|
|
425
|
+
hour12: false,
|
|
426
|
+
hour: '2-digit',
|
|
427
|
+
minute: '2-digit',
|
|
428
|
+
})
|
|
429
|
+
: '',
|
|
414
430
|
agent: (e.agent || 'unknown').replace('agileflow-', ''),
|
|
415
431
|
msg: e.message || (e.event === 'iteration' ? `Iter ${e.iter}` : e.event || ''),
|
|
416
432
|
})),
|
|
@@ -422,15 +438,20 @@ class SimpleTUI {
|
|
|
422
438
|
].slice(-(panelHeight - 2));
|
|
423
439
|
|
|
424
440
|
if (allMessages.length === 0) {
|
|
425
|
-
lines.push(
|
|
441
|
+
lines.push(
|
|
442
|
+
`${ANSI.green}│${ANSI.reset} ${ANSI.dim}${pad('Waiting for activity...', innerWidth)}${ANSI.reset}${ANSI.green}│${ANSI.reset}`
|
|
443
|
+
);
|
|
426
444
|
} else {
|
|
427
445
|
for (const msg of allMessages) {
|
|
428
446
|
const prefix = `${msg.time} [${msg.agent}] `;
|
|
429
447
|
const maxMsgLen = Math.max(10, innerWidth - prefix.length - 1);
|
|
430
|
-
const truncatedMsg =
|
|
448
|
+
const truncatedMsg =
|
|
449
|
+
msg.msg.length > maxMsgLen ? msg.msg.slice(0, maxMsgLen - 2) + '..' : msg.msg;
|
|
431
450
|
const line = `${ANSI.dim}${msg.time}${ANSI.reset} [${ANSI.cyan}${msg.agent}${ANSI.reset}] ${truncatedMsg}`;
|
|
432
451
|
const cleanLen = prefix.length + truncatedMsg.length;
|
|
433
|
-
lines.push(
|
|
452
|
+
lines.push(
|
|
453
|
+
`${ANSI.green}│${ANSI.reset} ${line}${' '.repeat(Math.max(0, innerWidth - cleanLen - 1))}${ANSI.green}│${ANSI.reset}`
|
|
454
|
+
);
|
|
434
455
|
}
|
|
435
456
|
}
|
|
436
457
|
|
|
@@ -450,16 +471,23 @@ class SimpleTUI {
|
|
|
450
471
|
|
|
451
472
|
// Loop status
|
|
452
473
|
if (loopStatus.active) {
|
|
453
|
-
const statusIcon = loopStatus.paused
|
|
454
|
-
|
|
474
|
+
const statusIcon = loopStatus.paused
|
|
475
|
+
? `${ANSI.yellow}⏸${ANSI.reset}`
|
|
476
|
+
: `${ANSI.green}▶${ANSI.reset}`;
|
|
477
|
+
const bar = progressBar(
|
|
478
|
+
loopStatus.iteration,
|
|
479
|
+
loopStatus.maxIterations,
|
|
480
|
+
Math.min(15, Math.floor(width / 6))
|
|
481
|
+
);
|
|
455
482
|
const info = `${loopStatus.epic || ''}${loopStatus.currentStory ? ' / ' + loopStatus.currentStory : ''}`;
|
|
456
483
|
output.push(`${statusIcon} ${ANSI.bold}${info}${ANSI.reset} ${bar}`);
|
|
457
484
|
}
|
|
458
485
|
|
|
459
486
|
// Key bindings - compact on narrow terminals
|
|
460
|
-
const keys =
|
|
461
|
-
|
|
462
|
-
|
|
487
|
+
const keys =
|
|
488
|
+
width >= 70
|
|
489
|
+
? `${ANSI.dim}[Q]uit [S]tart [P]ause [R]esume [T]race [1-9]Sessions${ANSI.reset}`
|
|
490
|
+
: `${ANSI.dim}Q S P R T 1-9${ANSI.reset}`;
|
|
463
491
|
const version = `${ANSI.cyan}v2.90.3${ANSI.reset}`;
|
|
464
492
|
output.push(`${keys} ${version}`);
|
|
465
493
|
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AgileFlow Dashboard
|
|
5
|
+
*
|
|
6
|
+
* Static dashboard view - prints once and exits.
|
|
7
|
+
* No continuous refresh, no screen clearing, scrollable output.
|
|
8
|
+
*
|
|
9
|
+
* Usage: npx agileflow start
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
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
|
+
|
|
133
|
+
const status = await loadStatus();
|
|
134
|
+
const loopStatus = loadLoopStatus();
|
|
135
|
+
|
|
136
|
+
if (!status) {
|
|
137
|
+
console.log(
|
|
138
|
+
`${colors.dim} No status.json found. Run /agileflow:story to create stories.${colors.reset}`
|
|
139
|
+
);
|
|
140
|
+
console.log('');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Count stories by status
|
|
145
|
+
const stories = Object.values(status).filter(
|
|
146
|
+
s => s && typeof s === 'object' && (s.id || s.story_id)
|
|
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;
|
|
157
|
+
|
|
158
|
+
// Summary Section
|
|
159
|
+
console.log(`${colors.bold} SUMMARY${colors.reset}`);
|
|
160
|
+
console.log(` ${'─'.repeat(45)}`);
|
|
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('');
|
|
166
|
+
|
|
167
|
+
// Loop Status (if active)
|
|
168
|
+
if (loopStatus && loopStatus.state && loopStatus.state !== 'idle') {
|
|
169
|
+
const stateColor =
|
|
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)}`);
|
|
182
|
+
}
|
|
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
|
+
|
|
197
|
+
// Blocked Stories
|
|
198
|
+
const blockedStories = stories.filter(s => s.status === 'blocked');
|
|
199
|
+
if (blockedStories.length > 0) {
|
|
200
|
+
console.log(`${colors.bold} ${colors.red}BLOCKED${colors.reset}`);
|
|
201
|
+
console.log(` ${'─'.repeat(45)}`);
|
|
202
|
+
blockedStories.forEach(story => {
|
|
203
|
+
console.log(formatStory(story, compact));
|
|
204
|
+
});
|
|
205
|
+
console.log('');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Ready Stories (up to 5)
|
|
209
|
+
const readyStories = stories.filter(s => s.status === 'ready').slice(0, 5);
|
|
210
|
+
if (readyStories.length > 0) {
|
|
211
|
+
const moreReady = counts.ready > 5 ? ` ${colors.dim}(+${counts.ready - 5} more)${colors.reset}` : '';
|
|
212
|
+
console.log(`${colors.bold} ${colors.cyan}READY${colors.reset}${moreReady}`);
|
|
213
|
+
console.log(` ${'─'.repeat(45)}`);
|
|
214
|
+
readyStories.forEach(story => {
|
|
215
|
+
console.log(formatStory(story, compact));
|
|
216
|
+
});
|
|
217
|
+
console.log('');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Completed Stories (last 3)
|
|
221
|
+
const completedStories = stories
|
|
222
|
+
.filter(s => ['completed', 'done'].includes(s.status))
|
|
223
|
+
.slice(-3)
|
|
224
|
+
.reverse();
|
|
225
|
+
if (completedStories.length > 0) {
|
|
226
|
+
const moreCompleted =
|
|
227
|
+
counts.completed > 3 ? ` ${colors.dim}(+${counts.completed - 3} more)${colors.reset}` : '';
|
|
228
|
+
console.log(`${colors.bold} ${colors.green}RECENTLY COMPLETED${colors.reset}${moreCompleted}`);
|
|
229
|
+
console.log(` ${'─'.repeat(45)}`);
|
|
230
|
+
completedStories.forEach(story => {
|
|
231
|
+
console.log(formatStory(story, compact));
|
|
232
|
+
});
|
|
233
|
+
console.log('');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Footer
|
|
237
|
+
console.log(`${colors.dim} ─────────────────────────────────────────────${colors.reset}`);
|
|
238
|
+
console.log(`${colors.dim} /agileflow:board Interactive kanban view${colors.reset}`);
|
|
239
|
+
console.log(`${colors.dim} /agileflow:story:list Full story listing${colors.reset}`);
|
|
240
|
+
console.log(`${colors.dim} npx agileflow tui Live updating dashboard${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 Dashboard${colors.reset}`);
|
|
251
|
+
console.log('');
|
|
252
|
+
console.log(`${colors.bold}Usage:${colors.reset}`);
|
|
253
|
+
console.log(' npx agileflow start Show dashboard (static)');
|
|
254
|
+
console.log(' npx agileflow start --compact Compact view');
|
|
255
|
+
console.log(' npx agileflow start --help Show this help');
|
|
256
|
+
console.log('');
|
|
257
|
+
console.log(`${colors.bold}See also:${colors.reset}`);
|
|
258
|
+
console.log(' npx agileflow tui Live updating dashboard');
|
|
259
|
+
console.log('');
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const compact = args.includes('--compact') || args.includes('-c');
|
|
264
|
+
|
|
265
|
+
// Get version from package.json
|
|
266
|
+
let version;
|
|
267
|
+
try {
|
|
268
|
+
const pkgPath = path.join(__dirname, '..', '..', '..', 'package.json');
|
|
269
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
270
|
+
version = pkg.version;
|
|
271
|
+
} catch (e) {
|
|
272
|
+
version = null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
await showDashboard({ version, compact, ...options });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Run directly if executed as script
|
|
279
|
+
if (require.main === module) {
|
|
280
|
+
main().catch(err => {
|
|
281
|
+
console.error(`${colors.red}Error:${colors.reset}`, err.message);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = {
|
|
287
|
+
name: 'start',
|
|
288
|
+
description: 'Show project dashboard (static view)',
|
|
289
|
+
action: main,
|
|
290
|
+
};
|