agileflow 2.90.7 → 2.92.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 +10 -0
- package/README.md +6 -6
- package/lib/README.md +178 -0
- package/lib/codebase-indexer.js +818 -0
- package/lib/colors.js +190 -12
- package/lib/consent.js +232 -0
- package/lib/correlation.js +277 -0
- package/lib/error-codes.js +46 -0
- package/lib/errors.js +48 -6
- package/lib/file-cache.js +182 -0
- package/lib/format-error.js +156 -0
- package/lib/path-resolver.js +155 -7
- package/lib/paths.js +212 -20
- package/lib/placeholder-registry.js +205 -0
- package/lib/registry-di.js +358 -0
- package/lib/result-schema.js +363 -0
- package/lib/result.js +210 -0
- package/lib/session-registry.js +13 -0
- package/lib/session-state-machine.js +465 -0
- package/lib/validate-commands.js +308 -0
- package/lib/validate-names.js +3 -3
- package/lib/validate.js +116 -52
- package/package.json +4 -1
- package/scripts/af +34 -0
- package/scripts/agent-loop.js +63 -9
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +435 -23
- package/scripts/archive-completed-stories.sh +57 -11
- package/scripts/claude-tmux.sh +102 -0
- package/scripts/damage-control-bash.js +3 -70
- package/scripts/damage-control-edit.js +3 -20
- package/scripts/damage-control-write.js +3 -20
- package/scripts/dependency-check.js +310 -0
- package/scripts/get-env.js +11 -4
- package/scripts/lib/configure-detect.js +23 -1
- package/scripts/lib/configure-features.js +43 -2
- package/scripts/lib/context-formatter.js +771 -0
- package/scripts/lib/context-loader.js +699 -0
- package/scripts/lib/damage-control-utils.js +107 -0
- package/scripts/lib/json-utils.sh +162 -0
- package/scripts/lib/state-migrator.js +353 -0
- package/scripts/lib/story-state-machine.js +437 -0
- package/scripts/obtain-context.js +118 -1048
- package/scripts/pre-push-check.sh +46 -0
- package/scripts/precompact-context.sh +36 -11
- package/scripts/query-codebase.js +538 -0
- package/scripts/ralph-loop.js +5 -5
- package/scripts/session-manager.js +220 -42
- package/scripts/spawn-parallel.js +651 -0
- package/scripts/tui/blessed/data/watcher.js +180 -0
- package/scripts/tui/blessed/index.js +244 -0
- package/scripts/tui/blessed/panels/output.js +101 -0
- package/scripts/tui/blessed/panels/sessions.js +150 -0
- package/scripts/tui/blessed/panels/trace.js +97 -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 +47 -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 +239 -0
- package/scripts/validators/json-schema-validator.js +186 -0
- package/scripts/validators/markdown-validator.js +152 -0
- package/scripts/validators/migration-validator.js +129 -0
- package/scripts/validators/security-validator.js +380 -0
- package/scripts/validators/story-format-validator.js +197 -0
- package/scripts/validators/test-result-validator.js +114 -0
- package/scripts/validators/workflow-validator.js +247 -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 +261 -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/adr.md +114 -0
- package/src/core/commands/agent.md +120 -0
- package/src/core/commands/assign.md +145 -0
- package/src/core/commands/audit.md +401 -0
- package/src/core/commands/babysit.md +32 -5
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/changelog.md +118 -0
- package/src/core/commands/configure.md +42 -6
- package/src/core/commands/diagnose.md +114 -0
- package/src/core/commands/epic.md +205 -1
- package/src/core/commands/handoff.md +128 -0
- package/src/core/commands/help.md +76 -0
- package/src/core/commands/metrics.md +1 -0
- package/src/core/commands/pr.md +96 -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/roadmap/analyze.md +400 -0
- package/src/core/commands/session/new.md +113 -6
- package/src/core/commands/session/spawn.md +197 -0
- package/src/core/commands/sprint.md +22 -0
- package/src/core/commands/status.md +200 -1
- package/src/core/commands/story/list.md +9 -9
- package/src/core/commands/story/view.md +1 -0
- package/src/core/commands/story.md +143 -4
- 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
- package/src/core/templates/agileflow-metadata.json +55 -2
- package/src/core/templates/plan-template.md +125 -0
- package/src/core/templates/story-lifecycle.md +213 -0
- package/src/core/templates/story-template.md +4 -0
- package/src/core/templates/tdd-test-template.js +241 -0
- package/tools/cli/commands/setup.js +86 -0
- package/tools/cli/installers/core/installer.js +94 -0
- package/tools/cli/installers/ide/_base-ide.js +20 -11
- package/tools/cli/installers/ide/codex.js +29 -47
- package/tools/cli/lib/config-manager.js +17 -2
- package/tools/cli/lib/content-transformer.js +271 -0
- package/tools/cli/lib/error-handler.js +14 -22
- package/tools/cli/lib/ide-error-factory.js +421 -0
- package/tools/cli/lib/ide-health-monitor.js +364 -0
- package/tools/cli/lib/ide-registry.js +114 -1
- package/tools/cli/lib/ui.js +14 -25
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const chokidar = require('chokidar');
|
|
6
|
+
|
|
7
|
+
// Import centralized path utilities
|
|
8
|
+
const { getStatusPath, getBusLogPath, getSessionStatePath } = require('../../../lib/paths');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DataWatcher - watches status.json and log files for changes
|
|
12
|
+
* Triggers refresh callback when data updates
|
|
13
|
+
*/
|
|
14
|
+
class DataWatcher {
|
|
15
|
+
constructor(state, onUpdate) {
|
|
16
|
+
this.state = state;
|
|
17
|
+
this.onUpdate = onUpdate;
|
|
18
|
+
this.watcher = null;
|
|
19
|
+
this.cwd = process.cwd();
|
|
20
|
+
|
|
21
|
+
// Paths to watch (using centralized path utilities)
|
|
22
|
+
this.statusPath = getStatusPath(this.cwd);
|
|
23
|
+
this.logPath = getBusLogPath(this.cwd);
|
|
24
|
+
this.sessionStatePath = getSessionStatePath(this.cwd);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
start() {
|
|
28
|
+
// Initial load
|
|
29
|
+
this.loadStatus();
|
|
30
|
+
this.loadLogs();
|
|
31
|
+
this.loadSessionState();
|
|
32
|
+
|
|
33
|
+
// Set up file watcher
|
|
34
|
+
const watchPaths = [this.statusPath, this.logPath, this.sessionStatePath].filter(p => {
|
|
35
|
+
const dir = path.dirname(p);
|
|
36
|
+
return fs.existsSync(dir);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (watchPaths.length === 0) {
|
|
40
|
+
// No paths to watch yet, check periodically
|
|
41
|
+
this.pollInterval = setInterval(() => {
|
|
42
|
+
this.loadStatus();
|
|
43
|
+
this.loadLogs();
|
|
44
|
+
this.loadSessionState();
|
|
45
|
+
this.onUpdate();
|
|
46
|
+
}, 2000);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.watcher = chokidar.watch(watchPaths, {
|
|
51
|
+
persistent: true,
|
|
52
|
+
ignoreInitial: true,
|
|
53
|
+
usePolling: false,
|
|
54
|
+
awaitWriteFinish: {
|
|
55
|
+
stabilityThreshold: 100,
|
|
56
|
+
pollInterval: 50,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.watcher.on('change', filePath => {
|
|
61
|
+
if (filePath.endsWith('status.json')) {
|
|
62
|
+
this.loadStatus();
|
|
63
|
+
} else if (filePath.endsWith('log.jsonl')) {
|
|
64
|
+
this.loadLogs();
|
|
65
|
+
} else if (filePath.endsWith('session-state.json')) {
|
|
66
|
+
this.loadSessionState();
|
|
67
|
+
}
|
|
68
|
+
this.onUpdate();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.watcher.on('error', err => {
|
|
72
|
+
// Silently handle watcher errors
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
loadStatus() {
|
|
77
|
+
try {
|
|
78
|
+
if (fs.existsSync(this.statusPath)) {
|
|
79
|
+
const content = fs.readFileSync(this.statusPath, 'utf8');
|
|
80
|
+
const data = JSON.parse(content);
|
|
81
|
+
|
|
82
|
+
// Extract stories with in_progress status as "sessions"
|
|
83
|
+
const stories = data.stories || [];
|
|
84
|
+
this.state.sessions = stories
|
|
85
|
+
.filter(s => s.status === 'in_progress')
|
|
86
|
+
.map((s, i) => ({
|
|
87
|
+
id: `S-${i + 1}`,
|
|
88
|
+
story: s.id || s.title || 'Unknown',
|
|
89
|
+
status: 'active',
|
|
90
|
+
duration: this.formatDuration(s.started_at),
|
|
91
|
+
progress: s.progress || '--',
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
// If no active stories, show all stories summary
|
|
95
|
+
if (this.state.sessions.length === 0) {
|
|
96
|
+
const ready = stories.filter(s => s.status === 'ready').length;
|
|
97
|
+
const completed = stories.filter(s => s.status === 'completed').length;
|
|
98
|
+
this.state.sessions = [
|
|
99
|
+
{
|
|
100
|
+
id: '--',
|
|
101
|
+
story: `${ready} ready, ${completed} completed`,
|
|
102
|
+
status: 'idle',
|
|
103
|
+
duration: '--',
|
|
104
|
+
progress: '--',
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
// Ignore parse errors
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
loadLogs() {
|
|
115
|
+
try {
|
|
116
|
+
if (fs.existsSync(this.logPath)) {
|
|
117
|
+
const content = fs.readFileSync(this.logPath, 'utf8');
|
|
118
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
119
|
+
|
|
120
|
+
// Take last 100 lines
|
|
121
|
+
this.state.logs = lines.slice(-100).map(line => {
|
|
122
|
+
try {
|
|
123
|
+
return JSON.parse(line);
|
|
124
|
+
} catch {
|
|
125
|
+
return { message: line, level: 'info' };
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
// Ignore read errors
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
loadSessionState() {
|
|
135
|
+
try {
|
|
136
|
+
if (fs.existsSync(this.sessionStatePath)) {
|
|
137
|
+
const content = fs.readFileSync(this.sessionStatePath, 'utf8');
|
|
138
|
+
const data = JSON.parse(content);
|
|
139
|
+
|
|
140
|
+
// Extract traces from active commands
|
|
141
|
+
if (data.active_commands) {
|
|
142
|
+
this.state.traces = data.active_commands.map((cmd, i) => ({
|
|
143
|
+
action: cmd.command || cmd,
|
|
144
|
+
status: 'running',
|
|
145
|
+
duration: '--',
|
|
146
|
+
details: cmd.args || '',
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
// Ignore errors
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
formatDuration(startedAt) {
|
|
156
|
+
if (!startedAt) return '--';
|
|
157
|
+
try {
|
|
158
|
+
const start = new Date(startedAt);
|
|
159
|
+
const now = new Date();
|
|
160
|
+
const diff = Math.floor((now - start) / 1000);
|
|
161
|
+
|
|
162
|
+
if (diff < 60) return `${diff}s`;
|
|
163
|
+
if (diff < 3600) return `${Math.floor(diff / 60)}m`;
|
|
164
|
+
return `${Math.floor(diff / 3600)}h ${Math.floor((diff % 3600) / 60)}m`;
|
|
165
|
+
} catch {
|
|
166
|
+
return '--';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
stop() {
|
|
171
|
+
if (this.watcher) {
|
|
172
|
+
this.watcher.close();
|
|
173
|
+
}
|
|
174
|
+
if (this.pollInterval) {
|
|
175
|
+
clearInterval(this.pollInterval);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = DataWatcher;
|
|
@@ -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,101 @@
|
|
|
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() {
|
|
79
|
+
box.show();
|
|
80
|
+
},
|
|
81
|
+
hide() {
|
|
82
|
+
box.hide();
|
|
83
|
+
},
|
|
84
|
+
focus() {
|
|
85
|
+
box.focus();
|
|
86
|
+
},
|
|
87
|
+
setData(data) {
|
|
88
|
+
logs = data || [];
|
|
89
|
+
render();
|
|
90
|
+
},
|
|
91
|
+
addLine(text, level = 'info') {
|
|
92
|
+
logs.push({ message: text, level, timestamp: new Date().toISOString() });
|
|
93
|
+
if (logs.length > 200) logs.shift();
|
|
94
|
+
render();
|
|
95
|
+
},
|
|
96
|
+
clear() {
|
|
97
|
+
logs = [];
|
|
98
|
+
render();
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
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:
|
|
45
|
+
'{bold}{cyan-fg} ID Story Status Time{/}{/}',
|
|
46
|
+
tags: true,
|
|
47
|
+
style: {
|
|
48
|
+
bg: '#222222',
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Divider
|
|
53
|
+
blessed.box({
|
|
54
|
+
parent: box,
|
|
55
|
+
top: 1,
|
|
56
|
+
left: 0,
|
|
57
|
+
width: '100%-2',
|
|
58
|
+
height: 1,
|
|
59
|
+
content: ' ────────── ───────────────────────────────── ──────────── ──────',
|
|
60
|
+
style: {
|
|
61
|
+
fg: 'gray',
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Content area
|
|
66
|
+
const content = blessed.box({
|
|
67
|
+
parent: box,
|
|
68
|
+
top: 2,
|
|
69
|
+
left: 0,
|
|
70
|
+
width: '100%-2',
|
|
71
|
+
height: '100%-3',
|
|
72
|
+
tags: true,
|
|
73
|
+
style: {
|
|
74
|
+
fg: 'white',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
let sessions = [];
|
|
79
|
+
let selectedIndex = 0;
|
|
80
|
+
|
|
81
|
+
function render() {
|
|
82
|
+
if (sessions.length === 0) {
|
|
83
|
+
content.setContent(`
|
|
84
|
+
{gray-fg}No active sessions{/}
|
|
85
|
+
|
|
86
|
+
{cyan-fg}Quick Start:{/}
|
|
87
|
+
• Run {bold}/agileflow:start{/} to begin a session
|
|
88
|
+
• Use {bold}/agileflow:loop{/} for autonomous mode
|
|
89
|
+
• Press {bold}?{/} for help
|
|
90
|
+
`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let lines = [];
|
|
95
|
+
sessions.forEach((s, i) => {
|
|
96
|
+
const selected = i === selectedIndex;
|
|
97
|
+
const prefix = selected ? '{inverse}' : '';
|
|
98
|
+
const suffix = selected ? '{/inverse}' : '';
|
|
99
|
+
|
|
100
|
+
// Status with colors
|
|
101
|
+
let statusText = s.status || 'unknown';
|
|
102
|
+
if (statusText === 'active') statusText = '{green-fg}● active{/}';
|
|
103
|
+
else if (statusText === 'running') statusText = '{green-fg}● running{/}';
|
|
104
|
+
else if (statusText === 'paused') statusText = '{yellow-fg}◉ paused{/}';
|
|
105
|
+
else if (statusText === 'idle') statusText = '{gray-fg}○ idle{/}';
|
|
106
|
+
else if (statusText === 'error') statusText = '{red-fg}✖ error{/}';
|
|
107
|
+
else statusText = `{gray-fg}○ ${statusText}{/}`;
|
|
108
|
+
|
|
109
|
+
const id = (s.id || '--').padEnd(10);
|
|
110
|
+
const story = (s.story || 'No story').substring(0, 34).padEnd(34);
|
|
111
|
+
const time = (s.duration || '--').padStart(6);
|
|
112
|
+
|
|
113
|
+
lines.push(`${prefix} ${id} ${story} ${statusText.padEnd(22)} ${time}${suffix}`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
content.setContent(lines.join('\n'));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
element: box,
|
|
121
|
+
show() {
|
|
122
|
+
box.show();
|
|
123
|
+
},
|
|
124
|
+
hide() {
|
|
125
|
+
box.hide();
|
|
126
|
+
},
|
|
127
|
+
focus() {
|
|
128
|
+
box.focus();
|
|
129
|
+
},
|
|
130
|
+
setData(data) {
|
|
131
|
+
sessions = data || [];
|
|
132
|
+
render();
|
|
133
|
+
},
|
|
134
|
+
getSelected() {
|
|
135
|
+
return selectedIndex;
|
|
136
|
+
},
|
|
137
|
+
selectNext() {
|
|
138
|
+
if (selectedIndex < sessions.length - 1) {
|
|
139
|
+
selectedIndex++;
|
|
140
|
+
render();
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
selectPrev() {
|
|
144
|
+
if (selectedIndex > 0) {
|
|
145
|
+
selectedIndex--;
|
|
146
|
+
render();
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
};
|