agileflow 2.90.7 → 2.91.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 +6 -6
- package/lib/codebase-indexer.js +810 -0
- package/lib/validate-names.js +3 -3
- package/package.json +4 -1
- package/scripts/obtain-context.js +238 -0
- package/scripts/precompact-context.sh +13 -1
- package/scripts/query-codebase.js +430 -0
- package/scripts/tui/blessed/data/watcher.js +175 -0
- package/scripts/tui/blessed/index.js +244 -0
- package/scripts/tui/blessed/panels/output.js +95 -0
- package/scripts/tui/blessed/panels/sessions.js +143 -0
- package/scripts/tui/blessed/panels/trace.js +91 -0
- package/scripts/tui/blessed/ui/help.js +77 -0
- package/scripts/tui/blessed/ui/screen.js +52 -0
- package/scripts/tui/blessed/ui/statusbar.js +51 -0
- package/scripts/tui/blessed/ui/tabbar.js +99 -0
- package/scripts/tui/index.js +38 -30
- package/scripts/validators/README.md +143 -0
- package/scripts/validators/component-validator.js +212 -0
- package/scripts/validators/json-schema-validator.js +179 -0
- package/scripts/validators/markdown-validator.js +153 -0
- package/scripts/validators/migration-validator.js +117 -0
- package/scripts/validators/security-validator.js +276 -0
- package/scripts/validators/story-format-validator.js +176 -0
- package/scripts/validators/test-result-validator.js +99 -0
- package/scripts/validators/workflow-validator.js +240 -0
- package/src/core/agents/accessibility.md +6 -0
- package/src/core/agents/adr-writer.md +6 -0
- package/src/core/agents/analytics.md +6 -0
- package/src/core/agents/api.md +6 -0
- package/src/core/agents/ci.md +6 -0
- package/src/core/agents/codebase-query.md +237 -0
- package/src/core/agents/compliance.md +6 -0
- package/src/core/agents/configuration-damage-control.md +6 -0
- package/src/core/agents/configuration-visual-e2e.md +6 -0
- package/src/core/agents/database.md +10 -0
- package/src/core/agents/datamigration.md +6 -0
- package/src/core/agents/design.md +6 -0
- package/src/core/agents/devops.md +6 -0
- package/src/core/agents/documentation.md +6 -0
- package/src/core/agents/epic-planner.md +6 -0
- package/src/core/agents/integrations.md +6 -0
- package/src/core/agents/mentor.md +6 -0
- package/src/core/agents/mobile.md +6 -0
- package/src/core/agents/monitoring.md +6 -0
- package/src/core/agents/multi-expert.md +6 -0
- package/src/core/agents/performance.md +6 -0
- package/src/core/agents/product.md +6 -0
- package/src/core/agents/qa.md +6 -0
- package/src/core/agents/readme-updater.md +6 -0
- package/src/core/agents/refactor.md +6 -0
- package/src/core/agents/research.md +6 -0
- package/src/core/agents/security.md +6 -0
- package/src/core/agents/testing.md +10 -0
- package/src/core/agents/ui.md +6 -0
- package/src/core/commands/audit.md +401 -0
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/epic.md +92 -1
- package/src/core/commands/help.md +1 -0
- package/src/core/commands/metrics.md +1 -0
- package/src/core/commands/research/analyze.md +1 -0
- package/src/core/commands/research/ask.md +2 -0
- package/src/core/commands/research/import.md +1 -0
- package/src/core/commands/research/list.md +2 -0
- package/src/core/commands/research/synthesize.md +584 -0
- package/src/core/commands/research/view.md +2 -0
- package/src/core/commands/status.md +126 -1
- package/src/core/commands/story/list.md +9 -9
- package/src/core/commands/story/view.md +1 -0
- package/src/core/experts/codebase-query/expertise.yaml +190 -0
- package/src/core/experts/codebase-query/question.md +73 -0
- package/src/core/experts/codebase-query/self-improve.md +105 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AgileFlow TUI - Terminal User Interface
|
|
6
|
+
*
|
|
7
|
+
* Full-screen, flicker-free dashboard with tabs and live updates.
|
|
8
|
+
*
|
|
9
|
+
* Key bindings:
|
|
10
|
+
* 1-3 Switch tabs (Sessions, Output, Trace)
|
|
11
|
+
* Tab Next tab
|
|
12
|
+
* j/k Navigate items
|
|
13
|
+
* r Refresh data
|
|
14
|
+
* ?/h Toggle help overlay
|
|
15
|
+
* q Quit TUI
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const blessed = require('blessed');
|
|
19
|
+
|
|
20
|
+
// UI components
|
|
21
|
+
const createScreen = require('./ui/screen');
|
|
22
|
+
const createTabBar = require('./ui/tabbar');
|
|
23
|
+
const createStatusBar = require('./ui/statusbar');
|
|
24
|
+
const createHelpOverlay = require('./ui/help');
|
|
25
|
+
|
|
26
|
+
// Panels
|
|
27
|
+
const createSessionsPanel = require('./panels/sessions');
|
|
28
|
+
const createOutputPanel = require('./panels/output');
|
|
29
|
+
const createTracePanel = require('./panels/trace');
|
|
30
|
+
|
|
31
|
+
// Data
|
|
32
|
+
const DataWatcher = require('./data/watcher');
|
|
33
|
+
|
|
34
|
+
class AgileFlowTUI {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.state = {
|
|
37
|
+
activeTab: 0,
|
|
38
|
+
tabs: ['Sessions', 'Output', 'Trace'],
|
|
39
|
+
sessions: [],
|
|
40
|
+
logs: [],
|
|
41
|
+
traces: [],
|
|
42
|
+
showHelp: false
|
|
43
|
+
};
|
|
44
|
+
this.components = {};
|
|
45
|
+
this.watcher = null;
|
|
46
|
+
this.screen = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
init() {
|
|
50
|
+
// Create screen with flicker-free rendering
|
|
51
|
+
this.screen = createScreen();
|
|
52
|
+
|
|
53
|
+
// Wrapper to pass screen to panels (they expect grid.screen)
|
|
54
|
+
const gridLike = { screen: this.screen };
|
|
55
|
+
|
|
56
|
+
// Create UI components
|
|
57
|
+
this.components.tabBar = createTabBar(this.screen, this.state);
|
|
58
|
+
this.components.statusBar = createStatusBar(this.screen, this.state);
|
|
59
|
+
this.components.help = createHelpOverlay(this.screen, this.state);
|
|
60
|
+
|
|
61
|
+
// Create panels (pass screen wrapper)
|
|
62
|
+
this.components.sessions = createSessionsPanel(gridLike, this.state);
|
|
63
|
+
this.components.output = createOutputPanel(gridLike, this.state);
|
|
64
|
+
this.components.trace = createTracePanel(gridLike, this.state);
|
|
65
|
+
|
|
66
|
+
// Hide all panels initially
|
|
67
|
+
this.components.sessions.hide();
|
|
68
|
+
this.components.output.hide();
|
|
69
|
+
this.components.trace.hide();
|
|
70
|
+
|
|
71
|
+
// Set up keybindings
|
|
72
|
+
this.setupKeys();
|
|
73
|
+
|
|
74
|
+
// Start data watcher for live updates
|
|
75
|
+
this.watcher = new DataWatcher(this.state, () => this.refresh());
|
|
76
|
+
this.watcher.start();
|
|
77
|
+
|
|
78
|
+
// Show initial tab
|
|
79
|
+
this.switchTab(0);
|
|
80
|
+
|
|
81
|
+
// Initial render
|
|
82
|
+
this.screen.render();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setupKeys() {
|
|
86
|
+
// Quit
|
|
87
|
+
this.screen.key(['q', 'C-c'], () => {
|
|
88
|
+
this.cleanup();
|
|
89
|
+
process.exit(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Tab switching with number keys
|
|
93
|
+
this.screen.key(['1', '2', '3'], (ch) => {
|
|
94
|
+
if (this.components.help.isVisible()) return;
|
|
95
|
+
this.switchTab(parseInt(ch) - 1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Tab switching with Tab key
|
|
99
|
+
this.screen.key(['tab'], () => {
|
|
100
|
+
if (this.components.help.isVisible()) return;
|
|
101
|
+
this.switchTab((this.state.activeTab + 1) % this.state.tabs.length);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Shift+Tab for reverse tab
|
|
105
|
+
this.screen.key(['S-tab'], () => {
|
|
106
|
+
if (this.components.help.isVisible()) return;
|
|
107
|
+
const prev = (this.state.activeTab - 1 + this.state.tabs.length) % this.state.tabs.length;
|
|
108
|
+
this.switchTab(prev);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Help overlay
|
|
112
|
+
this.screen.key(['?', 'h'], () => {
|
|
113
|
+
this.state.showHelp = !this.state.showHelp;
|
|
114
|
+
this.components.help.toggle();
|
|
115
|
+
this.screen.render();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Refresh
|
|
119
|
+
this.screen.key(['r'], () => {
|
|
120
|
+
if (this.components.help.isVisible()) return;
|
|
121
|
+
this.components.statusBar.setStatus('{yellow-fg}Refreshing...{/}');
|
|
122
|
+
this.screen.render();
|
|
123
|
+
|
|
124
|
+
// Reload data
|
|
125
|
+
if (this.watcher) {
|
|
126
|
+
this.watcher.loadStatus();
|
|
127
|
+
this.watcher.loadLogs();
|
|
128
|
+
this.watcher.loadSessionState();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
this.refresh();
|
|
133
|
+
this.components.statusBar.resetHints();
|
|
134
|
+
this.screen.render();
|
|
135
|
+
}, 100);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Navigation (j/k)
|
|
139
|
+
this.screen.key(['j', 'down'], () => {
|
|
140
|
+
if (this.components.help.isVisible()) return;
|
|
141
|
+
if (this.state.activeTab === 0 && this.components.sessions.selectNext) {
|
|
142
|
+
this.components.sessions.selectNext();
|
|
143
|
+
this.screen.render();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
this.screen.key(['k', 'up'], () => {
|
|
148
|
+
if (this.components.help.isVisible()) return;
|
|
149
|
+
if (this.state.activeTab === 0 && this.components.sessions.selectPrev) {
|
|
150
|
+
this.components.sessions.selectPrev();
|
|
151
|
+
this.screen.render();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Escape to close help
|
|
156
|
+
this.screen.key(['escape'], () => {
|
|
157
|
+
if (this.components.help.isVisible()) {
|
|
158
|
+
this.components.help.hide();
|
|
159
|
+
this.screen.render();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
switchTab(index) {
|
|
165
|
+
if (index < 0 || index >= this.state.tabs.length) return;
|
|
166
|
+
|
|
167
|
+
this.state.activeTab = index;
|
|
168
|
+
|
|
169
|
+
// Hide all panels
|
|
170
|
+
this.components.sessions.hide();
|
|
171
|
+
this.components.output.hide();
|
|
172
|
+
this.components.trace.hide();
|
|
173
|
+
|
|
174
|
+
// Show and focus active panel
|
|
175
|
+
switch (index) {
|
|
176
|
+
case 0:
|
|
177
|
+
this.components.sessions.show();
|
|
178
|
+
this.components.sessions.focus();
|
|
179
|
+
break;
|
|
180
|
+
case 1:
|
|
181
|
+
this.components.output.show();
|
|
182
|
+
this.components.output.focus();
|
|
183
|
+
break;
|
|
184
|
+
case 2:
|
|
185
|
+
this.components.trace.show();
|
|
186
|
+
this.components.trace.focus();
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Update tab bar highlighting
|
|
191
|
+
this.components.tabBar.setTab(index);
|
|
192
|
+
|
|
193
|
+
// Render changes
|
|
194
|
+
this.screen.render();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
refresh() {
|
|
198
|
+
// Update panels with current state
|
|
199
|
+
this.components.sessions.setData(this.state.sessions);
|
|
200
|
+
this.components.output.setData(this.state.logs);
|
|
201
|
+
this.components.trace.setData(this.state.traces);
|
|
202
|
+
|
|
203
|
+
// Re-render
|
|
204
|
+
this.screen.render();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
cleanup() {
|
|
208
|
+
// Stop watching files
|
|
209
|
+
if (this.watcher) {
|
|
210
|
+
this.watcher.stop();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Destroy screen
|
|
214
|
+
if (this.screen) {
|
|
215
|
+
this.screen.destroy();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
run() {
|
|
220
|
+
try {
|
|
221
|
+
this.init();
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.error('Failed to initialize TUI:', err.message);
|
|
224
|
+
console.error('');
|
|
225
|
+
console.error('Try running: npx agileflow tui --fallback');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Main entry point
|
|
233
|
+
*/
|
|
234
|
+
function main() {
|
|
235
|
+
const tui = new AgileFlowTUI();
|
|
236
|
+
tui.run();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Run if executed directly
|
|
240
|
+
if (require.main === module) {
|
|
241
|
+
main();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = { AgileFlowTUI, main };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const blessed = require('blessed');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create the output panel showing log stream
|
|
7
|
+
*/
|
|
8
|
+
module.exports = function createOutputPanel(grid, state) {
|
|
9
|
+
const box = blessed.box({
|
|
10
|
+
parent: grid.screen,
|
|
11
|
+
top: 3,
|
|
12
|
+
left: 0,
|
|
13
|
+
width: '100%',
|
|
14
|
+
height: '100%-4',
|
|
15
|
+
label: ' {green-fg}{bold}Output{/bold}{/green-fg} ',
|
|
16
|
+
tags: true,
|
|
17
|
+
border: {
|
|
18
|
+
type: 'line',
|
|
19
|
+
fg: 'green'
|
|
20
|
+
},
|
|
21
|
+
style: {
|
|
22
|
+
fg: 'white',
|
|
23
|
+
bg: 'black',
|
|
24
|
+
border: { fg: 'green' }
|
|
25
|
+
},
|
|
26
|
+
scrollable: true,
|
|
27
|
+
alwaysScroll: true,
|
|
28
|
+
scrollbar: {
|
|
29
|
+
ch: '│',
|
|
30
|
+
style: { fg: 'green' }
|
|
31
|
+
},
|
|
32
|
+
keys: true,
|
|
33
|
+
vi: true,
|
|
34
|
+
mouse: true
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let logs = [];
|
|
38
|
+
|
|
39
|
+
function render() {
|
|
40
|
+
if (logs.length === 0) {
|
|
41
|
+
box.setContent(`
|
|
42
|
+
{gray-fg}Waiting for output...{/}
|
|
43
|
+
|
|
44
|
+
{green-fg}What shows here:{/}
|
|
45
|
+
• Agent activity logs
|
|
46
|
+
• Command execution output
|
|
47
|
+
• Tool call results
|
|
48
|
+
• Error messages
|
|
49
|
+
|
|
50
|
+
{gray-fg}Logs are read from docs/09-agents/bus/log.jsonl{/}
|
|
51
|
+
`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let lines = [];
|
|
56
|
+
logs.forEach(entry => {
|
|
57
|
+
const time = entry.timestamp
|
|
58
|
+
? new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false })
|
|
59
|
+
: '--:--:--';
|
|
60
|
+
|
|
61
|
+
const level = entry.level || 'info';
|
|
62
|
+
let levelTag = '{gray-fg}INFO{/}';
|
|
63
|
+
if (level === 'error') levelTag = '{red-fg}ERRO{/}';
|
|
64
|
+
else if (level === 'warn' || level === 'warning') levelTag = '{yellow-fg}WARN{/}';
|
|
65
|
+
else if (level === 'debug') levelTag = '{gray-fg}DEBG{/}';
|
|
66
|
+
else if (level === 'success') levelTag = '{green-fg}DONE{/}';
|
|
67
|
+
|
|
68
|
+
const msg = entry.message || (typeof entry === 'string' ? entry : JSON.stringify(entry));
|
|
69
|
+
lines.push(` {gray-fg}${time}{/} ${levelTag} ${msg}`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
box.setContent(lines.join('\n'));
|
|
73
|
+
box.setScrollPerc(100); // Auto-scroll to bottom
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
element: box,
|
|
78
|
+
show() { box.show(); },
|
|
79
|
+
hide() { box.hide(); },
|
|
80
|
+
focus() { box.focus(); },
|
|
81
|
+
setData(data) {
|
|
82
|
+
logs = data || [];
|
|
83
|
+
render();
|
|
84
|
+
},
|
|
85
|
+
addLine(text, level = 'info') {
|
|
86
|
+
logs.push({ message: text, level, timestamp: new Date().toISOString() });
|
|
87
|
+
if (logs.length > 200) logs.shift();
|
|
88
|
+
render();
|
|
89
|
+
},
|
|
90
|
+
clear() {
|
|
91
|
+
logs = [];
|
|
92
|
+
render();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const blessed = require('blessed');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create the sessions panel with a better styled list
|
|
7
|
+
*/
|
|
8
|
+
module.exports = function createSessionsPanel(grid, state) {
|
|
9
|
+
// Use a list instead of table for better control
|
|
10
|
+
const box = blessed.box({
|
|
11
|
+
parent: grid.screen,
|
|
12
|
+
top: 3,
|
|
13
|
+
left: 0,
|
|
14
|
+
width: '100%',
|
|
15
|
+
height: '100%-4',
|
|
16
|
+
label: ' {cyan-fg}{bold}Sessions{/bold}{/cyan-fg} ',
|
|
17
|
+
tags: true,
|
|
18
|
+
border: {
|
|
19
|
+
type: 'line',
|
|
20
|
+
fg: 'cyan'
|
|
21
|
+
},
|
|
22
|
+
style: {
|
|
23
|
+
fg: 'white',
|
|
24
|
+
bg: 'black',
|
|
25
|
+
border: { fg: 'cyan' }
|
|
26
|
+
},
|
|
27
|
+
scrollable: true,
|
|
28
|
+
alwaysScroll: true,
|
|
29
|
+
scrollbar: {
|
|
30
|
+
ch: '│',
|
|
31
|
+
style: { fg: 'cyan' }
|
|
32
|
+
},
|
|
33
|
+
keys: true,
|
|
34
|
+
vi: true
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Header row
|
|
38
|
+
const header = blessed.box({
|
|
39
|
+
parent: box,
|
|
40
|
+
top: 0,
|
|
41
|
+
left: 0,
|
|
42
|
+
width: '100%-2',
|
|
43
|
+
height: 1,
|
|
44
|
+
content: '{bold}{cyan-fg} ID Story Status Time{/}{/}',
|
|
45
|
+
tags: true,
|
|
46
|
+
style: {
|
|
47
|
+
bg: '#222222'
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Divider
|
|
52
|
+
blessed.box({
|
|
53
|
+
parent: box,
|
|
54
|
+
top: 1,
|
|
55
|
+
left: 0,
|
|
56
|
+
width: '100%-2',
|
|
57
|
+
height: 1,
|
|
58
|
+
content: ' ────────── ───────────────────────────────── ──────────── ──────',
|
|
59
|
+
style: {
|
|
60
|
+
fg: 'gray'
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Content area
|
|
65
|
+
const content = blessed.box({
|
|
66
|
+
parent: box,
|
|
67
|
+
top: 2,
|
|
68
|
+
left: 0,
|
|
69
|
+
width: '100%-2',
|
|
70
|
+
height: '100%-3',
|
|
71
|
+
tags: true,
|
|
72
|
+
style: {
|
|
73
|
+
fg: 'white'
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
let sessions = [];
|
|
78
|
+
let selectedIndex = 0;
|
|
79
|
+
|
|
80
|
+
function render() {
|
|
81
|
+
if (sessions.length === 0) {
|
|
82
|
+
content.setContent(`
|
|
83
|
+
{gray-fg}No active sessions{/}
|
|
84
|
+
|
|
85
|
+
{cyan-fg}Quick Start:{/}
|
|
86
|
+
• Run {bold}/agileflow:start{/} to begin a session
|
|
87
|
+
• Use {bold}/agileflow:loop{/} for autonomous mode
|
|
88
|
+
• Press {bold}?{/} for help
|
|
89
|
+
`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let lines = [];
|
|
94
|
+
sessions.forEach((s, i) => {
|
|
95
|
+
const selected = i === selectedIndex;
|
|
96
|
+
const prefix = selected ? '{inverse}' : '';
|
|
97
|
+
const suffix = selected ? '{/inverse}' : '';
|
|
98
|
+
|
|
99
|
+
// Status with colors
|
|
100
|
+
let statusText = s.status || 'unknown';
|
|
101
|
+
if (statusText === 'active') statusText = '{green-fg}● active{/}';
|
|
102
|
+
else if (statusText === 'running') statusText = '{green-fg}● running{/}';
|
|
103
|
+
else if (statusText === 'paused') statusText = '{yellow-fg}◉ paused{/}';
|
|
104
|
+
else if (statusText === 'idle') statusText = '{gray-fg}○ idle{/}';
|
|
105
|
+
else if (statusText === 'error') statusText = '{red-fg}✖ error{/}';
|
|
106
|
+
else statusText = `{gray-fg}○ ${statusText}{/}`;
|
|
107
|
+
|
|
108
|
+
const id = (s.id || '--').padEnd(10);
|
|
109
|
+
const story = (s.story || 'No story').substring(0, 34).padEnd(34);
|
|
110
|
+
const time = (s.duration || '--').padStart(6);
|
|
111
|
+
|
|
112
|
+
lines.push(`${prefix} ${id} ${story} ${statusText.padEnd(22)} ${time}${suffix}`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
content.setContent(lines.join('\n'));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
element: box,
|
|
120
|
+
show() { box.show(); },
|
|
121
|
+
hide() { box.hide(); },
|
|
122
|
+
focus() { box.focus(); },
|
|
123
|
+
setData(data) {
|
|
124
|
+
sessions = data || [];
|
|
125
|
+
render();
|
|
126
|
+
},
|
|
127
|
+
getSelected() {
|
|
128
|
+
return selectedIndex;
|
|
129
|
+
},
|
|
130
|
+
selectNext() {
|
|
131
|
+
if (selectedIndex < sessions.length - 1) {
|
|
132
|
+
selectedIndex++;
|
|
133
|
+
render();
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
selectPrev() {
|
|
137
|
+
if (selectedIndex > 0) {
|
|
138
|
+
selectedIndex--;
|
|
139
|
+
render();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const blessed = require('blessed');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create the trace panel showing execution steps
|
|
7
|
+
*/
|
|
8
|
+
module.exports = function createTracePanel(grid, state) {
|
|
9
|
+
const box = blessed.box({
|
|
10
|
+
parent: grid.screen,
|
|
11
|
+
top: 3,
|
|
12
|
+
left: 0,
|
|
13
|
+
width: '100%',
|
|
14
|
+
height: '100%-4',
|
|
15
|
+
label: ' {yellow-fg}{bold}Trace{/bold}{/yellow-fg} ',
|
|
16
|
+
tags: true,
|
|
17
|
+
border: {
|
|
18
|
+
type: 'line',
|
|
19
|
+
fg: 'yellow'
|
|
20
|
+
},
|
|
21
|
+
style: {
|
|
22
|
+
fg: 'white',
|
|
23
|
+
bg: 'black',
|
|
24
|
+
border: { fg: 'yellow' }
|
|
25
|
+
},
|
|
26
|
+
scrollable: true,
|
|
27
|
+
alwaysScroll: true,
|
|
28
|
+
scrollbar: {
|
|
29
|
+
ch: '│',
|
|
30
|
+
style: { fg: 'yellow' }
|
|
31
|
+
},
|
|
32
|
+
keys: true,
|
|
33
|
+
vi: true
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
let traces = [];
|
|
37
|
+
|
|
38
|
+
function render() {
|
|
39
|
+
if (traces.length === 0) {
|
|
40
|
+
box.setContent(`
|
|
41
|
+
{gray-fg}No trace data{/}
|
|
42
|
+
|
|
43
|
+
{yellow-fg}What shows here:{/}
|
|
44
|
+
• Active command execution
|
|
45
|
+
• Step-by-step agent workflow
|
|
46
|
+
• Tool calls and responses
|
|
47
|
+
• Timing information
|
|
48
|
+
|
|
49
|
+
{gray-fg}Trace data comes from .agileflow/session-state.json{/}
|
|
50
|
+
`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let lines = [];
|
|
55
|
+
traces.forEach((t, i) => {
|
|
56
|
+
// Status indicator
|
|
57
|
+
let statusIcon = '{gray-fg}○{/}';
|
|
58
|
+
if (t.status === 'running') statusIcon = '{yellow-fg}◉{/}';
|
|
59
|
+
else if (t.status === 'completed') statusIcon = '{green-fg}●{/}';
|
|
60
|
+
else if (t.status === 'error') statusIcon = '{red-fg}✖{/}';
|
|
61
|
+
|
|
62
|
+
const stepNum = String(i + 1).padStart(2, '0');
|
|
63
|
+
const action = (t.action || 'Unknown').substring(0, 40);
|
|
64
|
+
const duration = t.duration || '--';
|
|
65
|
+
|
|
66
|
+
lines.push(` ${statusIcon} Step ${stepNum}: {bold}${action}{/}`);
|
|
67
|
+
if (t.details) {
|
|
68
|
+
lines.push(` {gray-fg}${t.details.substring(0, 60)}{/}`);
|
|
69
|
+
}
|
|
70
|
+
lines.push(` {gray-fg}Duration: ${duration}{/}`);
|
|
71
|
+
lines.push('');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
box.setContent(lines.join('\n'));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
element: box,
|
|
79
|
+
show() { box.show(); },
|
|
80
|
+
hide() { box.hide(); },
|
|
81
|
+
focus() { box.focus(); },
|
|
82
|
+
setData(data) {
|
|
83
|
+
traces = data || [];
|
|
84
|
+
render();
|
|
85
|
+
},
|
|
86
|
+
addStep(action, status = 'running', details = '') {
|
|
87
|
+
traces.push({ action, status, details, duration: '--' });
|
|
88
|
+
render();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const blessed = require('blessed');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create the help overlay that shows all available commands
|
|
7
|
+
* Toggled with ? or h key
|
|
8
|
+
*/
|
|
9
|
+
module.exports = function createHelpOverlay(screen, state) {
|
|
10
|
+
const help = blessed.box({
|
|
11
|
+
parent: screen,
|
|
12
|
+
top: 'center',
|
|
13
|
+
left: 'center',
|
|
14
|
+
width: 55,
|
|
15
|
+
height: 22,
|
|
16
|
+
tags: true,
|
|
17
|
+
border: { type: 'line', fg: 'yellow' },
|
|
18
|
+
style: {
|
|
19
|
+
fg: 'white',
|
|
20
|
+
bg: 'black',
|
|
21
|
+
border: { fg: 'yellow' }
|
|
22
|
+
},
|
|
23
|
+
label: ' {yellow-fg}Help{/yellow-fg} ',
|
|
24
|
+
hidden: true,
|
|
25
|
+
content: `
|
|
26
|
+
{bold}Navigation{/bold}
|
|
27
|
+
{cyan-fg}1{/} Sessions tab
|
|
28
|
+
{cyan-fg}2{/} Output tab
|
|
29
|
+
{cyan-fg}3{/} Trace tab
|
|
30
|
+
{cyan-fg}Tab{/} Next tab
|
|
31
|
+
{cyan-fg}j/k or {/}{cyan-fg}arrow-down/arrow-up{/} Navigate list items
|
|
32
|
+
{cyan-fg}Enter{/} Select item
|
|
33
|
+
|
|
34
|
+
{bold}Actions{/bold}
|
|
35
|
+
{cyan-fg}r{/} Refresh data
|
|
36
|
+
{cyan-fg}s{/} Start loop on current story
|
|
37
|
+
{cyan-fg}p{/} Pause active loop
|
|
38
|
+
|
|
39
|
+
{bold}Display{/bold}
|
|
40
|
+
{cyan-fg}?{/} or {cyan-fg}h{/} Toggle this help
|
|
41
|
+
{cyan-fg}Escape{/} Close help/dialogs
|
|
42
|
+
|
|
43
|
+
{bold}Exit{/bold}
|
|
44
|
+
{cyan-fg}q{/} Quit TUI
|
|
45
|
+
{cyan-fg}Ctrl+C{/} Force quit
|
|
46
|
+
|
|
47
|
+
{gray-fg}Press Escape or ? to close{/gray-fg}`
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Close help on various keys
|
|
51
|
+
help.key(['escape', 'q', '?', 'h', 'enter', 'space'], () => {
|
|
52
|
+
help.hide();
|
|
53
|
+
screen.render();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
element: help,
|
|
58
|
+
toggle() {
|
|
59
|
+
if (help.hidden) {
|
|
60
|
+
help.show();
|
|
61
|
+
help.focus();
|
|
62
|
+
} else {
|
|
63
|
+
help.hide();
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
show() {
|
|
67
|
+
help.show();
|
|
68
|
+
help.focus();
|
|
69
|
+
},
|
|
70
|
+
hide() {
|
|
71
|
+
help.hide();
|
|
72
|
+
},
|
|
73
|
+
isVisible() {
|
|
74
|
+
return !help.hidden;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const blessed = require('blessed');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create the main blessed screen with flicker-free rendering
|
|
7
|
+
*
|
|
8
|
+
* Key settings:
|
|
9
|
+
* - smartCSR: Enable differential rendering (only update changed cells)
|
|
10
|
+
* - fullUnicode: Support Unicode characters like box drawing
|
|
11
|
+
*/
|
|
12
|
+
module.exports = function createScreen() {
|
|
13
|
+
const screen = blessed.screen({
|
|
14
|
+
smartCSR: true, // Key for flicker-free differential rendering
|
|
15
|
+
fullUnicode: true, // Support Unicode characters
|
|
16
|
+
title: 'AgileFlow TUI',
|
|
17
|
+
cursor: {
|
|
18
|
+
artificial: true,
|
|
19
|
+
blink: true,
|
|
20
|
+
shape: 'line'
|
|
21
|
+
},
|
|
22
|
+
debug: false,
|
|
23
|
+
warnings: false,
|
|
24
|
+
autoPadding: true,
|
|
25
|
+
dockBorders: true
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Enable synchronized output mode for atomic updates
|
|
29
|
+
// This batches all screen updates and flushes them at once
|
|
30
|
+
// Note: Not all terminals support this, but it gracefully degrades
|
|
31
|
+
try {
|
|
32
|
+
process.stdout.write('\x1b[?2026h');
|
|
33
|
+
} catch (e) {
|
|
34
|
+
// Ignore if terminal doesn't support
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Disable synchronized output on exit
|
|
38
|
+
process.on('exit', () => {
|
|
39
|
+
try {
|
|
40
|
+
process.stdout.write('\x1b[?2026l');
|
|
41
|
+
} catch (e) {
|
|
42
|
+
// Ignore
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Handle resize gracefully
|
|
47
|
+
screen.on('resize', () => {
|
|
48
|
+
screen.render();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return screen;
|
|
52
|
+
};
|