agileflow 2.90.5 → 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 CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.90.7] - 2026-01-17
11
+
12
+ ### Fixed
13
+ - Fix TUI bouncing with in-place rendering
14
+
15
+ ## [2.90.6] - 2026-01-17
16
+
17
+ ### Changed
18
+ - Static TUI dashboard (no bouncing)
19
+
10
20
  ## [2.90.5] - 2026-01-17
11
21
 
12
22
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.90.5",
3
+ "version": "2.90.7",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -20,8 +20,7 @@
20
20
  * 1-9 - Switch session focus
21
21
  */
22
22
 
23
- // Ink-based TUI is disabled due to React version conflicts in monorepo
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
- // Fallback to simple TUI (pure Node.js, no React)
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
- // Initial render
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
- // Clear screen
294
- output.push(ANSI.clear + ANSI.home);
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
- process.stdout.write(output.join('\n'));
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) {
@@ -2,58 +2,55 @@
2
2
  * AgileFlow CLI - TUI Command
3
3
  *
4
4
  * Launches the Terminal User Interface for real-time session monitoring.
5
+ * Uses React/Ink for a proper live-updating terminal UI.
5
6
  */
6
7
 
7
- const chalk = require('chalk');
8
8
  const path = require('node:path');
9
9
  const { spawn } = require('node:child_process');
10
- const fs = require('fs-extra');
10
+ const fs = require('fs');
11
11
 
12
12
  module.exports = {
13
13
  name: 'tui',
14
14
  description: 'Launch Terminal User Interface for session monitoring',
15
15
  options: [['-d, --directory <path>', 'Project directory (default: current directory)']],
16
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
- }
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
+ }
26
+
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');
30
+
31
+ if (!fs.existsSync(tuiScript)) {
32
+ console.error('Error: TUI script not found at', tuiScript);
56
33
  process.exit(1);
57
34
  }
35
+
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
+ },
45
+ });
46
+
47
+ child.on('error', err => {
48
+ console.error('Error launching TUI:', err.message);
49
+ process.exit(1);
50
+ });
51
+
52
+ child.on('exit', code => {
53
+ process.exit(code || 0);
54
+ });
58
55
  },
59
56
  };
@@ -1,290 +0,0 @@
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
- };