agileflow 2.89.3 → 2.90.1
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/lib/placeholder-registry.js +617 -0
- package/lib/smart-json-file.js +228 -1
- package/lib/table-formatter.js +519 -0
- package/lib/transient-status.js +374 -0
- package/lib/ui-manager.js +612 -0
- package/lib/validate-args.js +213 -0
- package/lib/validate-names.js +143 -0
- package/lib/validate-paths.js +434 -0
- package/lib/validate.js +37 -737
- package/package.json +3 -1
- package/scripts/check-update.js +17 -3
- package/scripts/lib/sessionRegistry.js +678 -0
- package/scripts/session-manager.js +77 -10
- package/scripts/tui/App.js +151 -0
- package/scripts/tui/index.js +31 -0
- package/scripts/tui/lib/crashRecovery.js +304 -0
- package/scripts/tui/lib/eventStream.js +309 -0
- package/scripts/tui/lib/keyboard.js +261 -0
- package/scripts/tui/lib/loopControl.js +371 -0
- package/scripts/tui/panels/OutputPanel.js +242 -0
- package/scripts/tui/panels/SessionPanel.js +170 -0
- package/scripts/tui/panels/TracePanel.js +298 -0
- package/scripts/tui/simple-tui.js +390 -0
- package/tools/cli/commands/config.js +7 -31
- package/tools/cli/commands/doctor.js +28 -39
- package/tools/cli/commands/list.js +47 -35
- package/tools/cli/commands/status.js +20 -38
- package/tools/cli/commands/tui.js +59 -0
- package/tools/cli/commands/uninstall.js +12 -39
- package/tools/cli/installers/core/installer.js +13 -0
- package/tools/cli/lib/command-context.js +382 -0
- package/tools/cli/lib/config-manager.js +394 -0
- package/tools/cli/lib/ide-registry.js +186 -0
- package/tools/cli/lib/npm-utils.js +17 -3
- package/tools/cli/lib/self-update.js +148 -0
- package/tools/cli/lib/validation-middleware.js +491 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AgileFlow Simple TUI - Terminal User Interface
|
|
6
|
+
*
|
|
7
|
+
* A simple terminal-based dashboard that works without React/ink dependencies.
|
|
8
|
+
* Uses ANSI escape codes for styling and keyboard input.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node scripts/tui/simple-tui.js
|
|
12
|
+
* npx agileflow tui
|
|
13
|
+
*
|
|
14
|
+
* Key bindings:
|
|
15
|
+
* q - Quit TUI
|
|
16
|
+
* s - Start loop on current story
|
|
17
|
+
* p - Pause active loop
|
|
18
|
+
* r - Resume paused loop
|
|
19
|
+
* t - Toggle trace panel
|
|
20
|
+
* 1-9 - Switch session focus
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const readline = require('readline');
|
|
26
|
+
|
|
27
|
+
// ANSI escape codes
|
|
28
|
+
const ANSI = {
|
|
29
|
+
clear: '\x1b[2J',
|
|
30
|
+
home: '\x1b[H',
|
|
31
|
+
reset: '\x1b[0m',
|
|
32
|
+
bold: '\x1b[1m',
|
|
33
|
+
dim: '\x1b[2m',
|
|
34
|
+
italic: '\x1b[3m',
|
|
35
|
+
underline: '\x1b[4m',
|
|
36
|
+
// Colors
|
|
37
|
+
black: '\x1b[30m',
|
|
38
|
+
red: '\x1b[31m',
|
|
39
|
+
green: '\x1b[32m',
|
|
40
|
+
yellow: '\x1b[33m',
|
|
41
|
+
blue: '\x1b[34m',
|
|
42
|
+
magenta: '\x1b[35m',
|
|
43
|
+
cyan: '\x1b[36m',
|
|
44
|
+
white: '\x1b[37m',
|
|
45
|
+
gray: '\x1b[90m',
|
|
46
|
+
// Backgrounds
|
|
47
|
+
bgBlack: '\x1b[40m',
|
|
48
|
+
bgRed: '\x1b[41m',
|
|
49
|
+
bgGreen: '\x1b[42m',
|
|
50
|
+
bgYellow: '\x1b[43m',
|
|
51
|
+
bgBlue: '\x1b[44m',
|
|
52
|
+
bgCyan: '\x1b[46m',
|
|
53
|
+
// Cursor
|
|
54
|
+
hideCursor: '\x1b[?25l',
|
|
55
|
+
showCursor: '\x1b[?25h',
|
|
56
|
+
saveCursor: '\x1b[s',
|
|
57
|
+
restoreCursor: '\x1b[u',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Get project root
|
|
61
|
+
function getProjectRoot() {
|
|
62
|
+
return process.cwd();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Get sessions from registry
|
|
66
|
+
function getSessions() {
|
|
67
|
+
try {
|
|
68
|
+
const registryPath = path.join(getProjectRoot(), '.agileflow', 'sessions', 'registry.json');
|
|
69
|
+
if (!fs.existsSync(registryPath)) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const data = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
73
|
+
const sessions = data.sessions || {};
|
|
74
|
+
// Convert object to array (registry stores sessions as object with ID keys)
|
|
75
|
+
if (Array.isArray(sessions)) {
|
|
76
|
+
return sessions;
|
|
77
|
+
}
|
|
78
|
+
return Object.entries(sessions).map(([id, session]) => ({
|
|
79
|
+
id,
|
|
80
|
+
...session,
|
|
81
|
+
}));
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Get loop status
|
|
88
|
+
function getLoopStatus() {
|
|
89
|
+
try {
|
|
90
|
+
const statePath = path.join(getProjectRoot(), 'docs', '09-agents', 'session-state.json');
|
|
91
|
+
if (!fs.existsSync(statePath)) {
|
|
92
|
+
return { active: false };
|
|
93
|
+
}
|
|
94
|
+
const data = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
95
|
+
const loop = data.ralph_loop;
|
|
96
|
+
if (!loop || !loop.enabled) {
|
|
97
|
+
return { active: false };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
active: true,
|
|
101
|
+
paused: loop.paused || false,
|
|
102
|
+
epic: loop.epic,
|
|
103
|
+
currentStory: loop.current_story,
|
|
104
|
+
iteration: loop.iteration || 0,
|
|
105
|
+
maxIterations: loop.max_iterations || 20,
|
|
106
|
+
};
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return { active: false };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Get recent agent events
|
|
113
|
+
function getAgentEvents(limit = 10) {
|
|
114
|
+
try {
|
|
115
|
+
const logPath = path.join(getProjectRoot(), 'docs', '09-agents', 'bus', 'log.jsonl');
|
|
116
|
+
if (!fs.existsSync(logPath)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const content = fs.readFileSync(logPath, 'utf8');
|
|
120
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
121
|
+
const events = [];
|
|
122
|
+
for (const line of lines.slice(-limit)) {
|
|
123
|
+
try {
|
|
124
|
+
events.push(JSON.parse(line));
|
|
125
|
+
} catch (e) {
|
|
126
|
+
// Skip invalid JSON
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return events;
|
|
130
|
+
} catch (e) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Draw box with border
|
|
136
|
+
function drawBox(x, y, width, height, title = '', color = 'cyan') {
|
|
137
|
+
const colorCode = ANSI[color] || ANSI.cyan;
|
|
138
|
+
const lines = [];
|
|
139
|
+
|
|
140
|
+
// Top border
|
|
141
|
+
lines.push(`${colorCode}+${'─'.repeat(width - 2)}+${ANSI.reset}`);
|
|
142
|
+
|
|
143
|
+
// Title if provided
|
|
144
|
+
if (title) {
|
|
145
|
+
const titleStr = ` ${title} `;
|
|
146
|
+
const paddingLeft = Math.floor((width - 2 - titleStr.length) / 2);
|
|
147
|
+
const paddingRight = width - 2 - titleStr.length - paddingLeft;
|
|
148
|
+
lines[0] = `${colorCode}+${'─'.repeat(paddingLeft)}${ANSI.bold}${titleStr}${ANSI.reset}${colorCode}${'─'.repeat(paddingRight)}+${ANSI.reset}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Content lines
|
|
152
|
+
for (let i = 0; i < height - 2; i++) {
|
|
153
|
+
lines.push(`${colorCode}│${ANSI.reset}${' '.repeat(width - 2)}${colorCode}│${ANSI.reset}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Bottom border
|
|
157
|
+
lines.push(`${colorCode}+${'─'.repeat(width - 2)}+${ANSI.reset}`);
|
|
158
|
+
|
|
159
|
+
return lines;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Pad string to width
|
|
163
|
+
function pad(str, width, align = 'left') {
|
|
164
|
+
const s = String(str).slice(0, width);
|
|
165
|
+
const padding = width - s.length;
|
|
166
|
+
if (align === 'right') return ' '.repeat(padding) + s;
|
|
167
|
+
if (align === 'center') return ' '.repeat(Math.floor(padding / 2)) + s + ' '.repeat(Math.ceil(padding / 2));
|
|
168
|
+
return s + ' '.repeat(padding);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Progress bar
|
|
172
|
+
function progressBar(value, max, width = 20) {
|
|
173
|
+
const percent = Math.min(100, Math.max(0, (value / max) * 100));
|
|
174
|
+
const filled = Math.round((percent / 100) * width);
|
|
175
|
+
const empty = width - filled;
|
|
176
|
+
|
|
177
|
+
let color = ANSI.red;
|
|
178
|
+
if (percent >= 80) color = ANSI.green;
|
|
179
|
+
else if (percent >= 50) color = ANSI.yellow;
|
|
180
|
+
|
|
181
|
+
return `${color}${'█'.repeat(filled)}${ANSI.dim}${'░'.repeat(empty)}${ANSI.reset} ${percent.toFixed(0)}%`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Main TUI class
|
|
185
|
+
class SimpleTUI {
|
|
186
|
+
constructor() {
|
|
187
|
+
this.running = false;
|
|
188
|
+
this.showTrace = true;
|
|
189
|
+
this.lastUpdate = new Date();
|
|
190
|
+
this.messages = [];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
start() {
|
|
194
|
+
this.running = true;
|
|
195
|
+
|
|
196
|
+
// Set up terminal
|
|
197
|
+
process.stdout.write(ANSI.clear + ANSI.home + ANSI.hideCursor);
|
|
198
|
+
|
|
199
|
+
// Enable raw mode for keyboard input
|
|
200
|
+
if (process.stdin.isTTY) {
|
|
201
|
+
readline.emitKeypressEvents(process.stdin);
|
|
202
|
+
process.stdin.setRawMode(true);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Handle keyboard input
|
|
206
|
+
process.stdin.on('keypress', (str, key) => {
|
|
207
|
+
this.handleKey(key);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Handle terminal resize
|
|
211
|
+
process.stdout.on('resize', () => {
|
|
212
|
+
this.render();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Initial render
|
|
216
|
+
this.render();
|
|
217
|
+
|
|
218
|
+
// Update loop
|
|
219
|
+
this.updateInterval = setInterval(() => {
|
|
220
|
+
this.render();
|
|
221
|
+
}, 2000);
|
|
222
|
+
|
|
223
|
+
this.addMessage('TUI', 'Dashboard started');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
stop() {
|
|
227
|
+
this.running = false;
|
|
228
|
+
|
|
229
|
+
if (this.updateInterval) {
|
|
230
|
+
clearInterval(this.updateInterval);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Restore terminal
|
|
234
|
+
process.stdout.write(ANSI.showCursor + ANSI.clear + ANSI.home);
|
|
235
|
+
|
|
236
|
+
if (process.stdin.isTTY) {
|
|
237
|
+
process.stdin.setRawMode(false);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log('AgileFlow TUI closed.');
|
|
241
|
+
process.exit(0);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
handleKey(key) {
|
|
245
|
+
if (!key) return;
|
|
246
|
+
|
|
247
|
+
const k = key.name || key.sequence;
|
|
248
|
+
|
|
249
|
+
switch (k) {
|
|
250
|
+
case 'q':
|
|
251
|
+
case 'escape':
|
|
252
|
+
this.stop();
|
|
253
|
+
break;
|
|
254
|
+
case 't':
|
|
255
|
+
this.showTrace = !this.showTrace;
|
|
256
|
+
this.addMessage('TUI', `Trace panel ${this.showTrace ? 'shown' : 'hidden'}`);
|
|
257
|
+
this.render();
|
|
258
|
+
break;
|
|
259
|
+
case 's':
|
|
260
|
+
this.addMessage('TUI', 'Start loop requested (not implemented)');
|
|
261
|
+
this.render();
|
|
262
|
+
break;
|
|
263
|
+
case 'p':
|
|
264
|
+
this.addMessage('TUI', 'Pause requested (not implemented)');
|
|
265
|
+
this.render();
|
|
266
|
+
break;
|
|
267
|
+
case 'r':
|
|
268
|
+
this.addMessage('TUI', 'Resume requested (not implemented)');
|
|
269
|
+
this.render();
|
|
270
|
+
break;
|
|
271
|
+
case 'c':
|
|
272
|
+
if (key.ctrl) {
|
|
273
|
+
this.stop();
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
addMessage(agent, message) {
|
|
280
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
281
|
+
this.messages.push({ timestamp, agent, message });
|
|
282
|
+
if (this.messages.length > 50) {
|
|
283
|
+
this.messages.shift();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
render() {
|
|
288
|
+
const width = process.stdout.columns || 80;
|
|
289
|
+
const height = process.stdout.rows || 24;
|
|
290
|
+
const output = [];
|
|
291
|
+
|
|
292
|
+
// Clear screen
|
|
293
|
+
output.push(ANSI.clear + ANSI.home);
|
|
294
|
+
|
|
295
|
+
// Header
|
|
296
|
+
const title = ' AgileFlow TUI ';
|
|
297
|
+
const headerPadding = Math.floor((width - title.length) / 2);
|
|
298
|
+
output.push(`${ANSI.bgCyan}${ANSI.black}${'═'.repeat(headerPadding)}${ANSI.bold}${title}${ANSI.reset}${ANSI.bgCyan}${ANSI.black}${'═'.repeat(width - headerPadding - title.length)}${ANSI.reset}`);
|
|
299
|
+
output.push('');
|
|
300
|
+
|
|
301
|
+
// Calculate panel widths
|
|
302
|
+
const leftWidth = Math.floor(width * 0.4);
|
|
303
|
+
const rightWidth = width - leftWidth - 1;
|
|
304
|
+
const panelHeight = height - 6; // Leave room for header and footer
|
|
305
|
+
|
|
306
|
+
// Get data
|
|
307
|
+
const sessions = getSessions();
|
|
308
|
+
const loopStatus = getLoopStatus();
|
|
309
|
+
const agentEvents = getAgentEvents(8);
|
|
310
|
+
|
|
311
|
+
// Build left panel (sessions)
|
|
312
|
+
output.push(`${ANSI.cyan}${ANSI.bold}┌─ SESSIONS ─${'─'.repeat(leftWidth - 14)}┐${ANSI.reset}`);
|
|
313
|
+
|
|
314
|
+
if (sessions.length === 0) {
|
|
315
|
+
output.push(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}No active sessions${ANSI.reset}${' '.repeat(leftWidth - 21)}${ANSI.cyan}│${ANSI.reset}`);
|
|
316
|
+
} else {
|
|
317
|
+
for (const session of sessions.slice(0, 5)) {
|
|
318
|
+
const indicator = session.current ? `${ANSI.green}>` : ' ';
|
|
319
|
+
const name = `Session ${session.id}${session.is_main ? ' [main]' : ''}`;
|
|
320
|
+
const branch = session.branch || 'unknown';
|
|
321
|
+
const story = session.story || 'none';
|
|
322
|
+
|
|
323
|
+
output.push(`${ANSI.cyan}│${ANSI.reset} ${indicator} ${ANSI.bold}${pad(name, leftWidth - 6)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
|
|
324
|
+
output.push(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}Branch:${ANSI.reset} ${ANSI.cyan}${pad(branch, leftWidth - 12)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
|
|
325
|
+
output.push(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}Story:${ANSI.reset} ${ANSI.yellow}${pad(story, leftWidth - 12)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Fill remaining space in left panel
|
|
330
|
+
const usedRows = sessions.length === 0 ? 1 : Math.min(sessions.length, 5) * 3;
|
|
331
|
+
const remainingRows = Math.max(0, panelHeight - usedRows - 2);
|
|
332
|
+
for (let i = 0; i < remainingRows; i++) {
|
|
333
|
+
output.push(`${ANSI.cyan}│${ANSI.reset}${' '.repeat(leftWidth - 2)}${ANSI.cyan}│${ANSI.reset}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
output.push(`${ANSI.cyan}└${'─'.repeat(leftWidth - 2)}┘${ANSI.reset}`);
|
|
337
|
+
|
|
338
|
+
// Move cursor to right panel position and draw
|
|
339
|
+
// For simplicity, we'll draw the right panel below the left panel
|
|
340
|
+
output.push('');
|
|
341
|
+
output.push(`${ANSI.green}${ANSI.bold}┌─ AGENT OUTPUT ─${'─'.repeat(width - 19)}┐${ANSI.reset}`);
|
|
342
|
+
|
|
343
|
+
if (agentEvents.length === 0 && this.messages.length === 0) {
|
|
344
|
+
output.push(`${ANSI.green}│${ANSI.reset} ${ANSI.dim}Waiting for agent activity...${ANSI.reset}${' '.repeat(width - 34)}${ANSI.green}│${ANSI.reset}`);
|
|
345
|
+
} else {
|
|
346
|
+
// Show recent events
|
|
347
|
+
const allMessages = [...agentEvents.map(e => ({
|
|
348
|
+
timestamp: e.timestamp ? new Date(e.timestamp).toLocaleTimeString() : '',
|
|
349
|
+
agent: e.agent || 'unknown',
|
|
350
|
+
message: e.message || (e.event === 'iteration' ? `Iteration ${e.iter}` : e.event || JSON.stringify(e))
|
|
351
|
+
})), ...this.messages].slice(-8);
|
|
352
|
+
|
|
353
|
+
for (const msg of allMessages) {
|
|
354
|
+
const line = `[${msg.timestamp}] [${ANSI.cyan}${msg.agent}${ANSI.reset}] ${msg.message}`;
|
|
355
|
+
const cleanLine = `[${msg.timestamp}] [${msg.agent}] ${msg.message}`;
|
|
356
|
+
const padding = width - cleanLine.length - 4;
|
|
357
|
+
output.push(`${ANSI.green}│${ANSI.reset} ${line}${' '.repeat(Math.max(0, padding))}${ANSI.green}│${ANSI.reset}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
output.push(`${ANSI.green}└${'─'.repeat(width - 2)}┘${ANSI.reset}`);
|
|
362
|
+
|
|
363
|
+
// Loop status (if active)
|
|
364
|
+
if (loopStatus.active) {
|
|
365
|
+
output.push('');
|
|
366
|
+
const statusIcon = loopStatus.paused ? `${ANSI.yellow}||${ANSI.reset}` : `${ANSI.green}>${ANSI.reset}`;
|
|
367
|
+
output.push(`${statusIcon} ${ANSI.bold}Loop:${ANSI.reset} ${loopStatus.epic || 'unknown'} | Story: ${loopStatus.currentStory || 'none'} | ${progressBar(loopStatus.iteration, loopStatus.maxIterations, 15)}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Footer with key bindings
|
|
371
|
+
output.push('');
|
|
372
|
+
output.push(`${ANSI.dim}[Q]uit [S]tart [P]ause [R]esume [T]race [1-9]Sessions ${ANSI.reset}${ANSI.cyan}AgileFlow v2.90.0${ANSI.reset}`);
|
|
373
|
+
|
|
374
|
+
// Output everything
|
|
375
|
+
process.stdout.write(output.join('\n'));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Main entry point
|
|
380
|
+
function main() {
|
|
381
|
+
const tui = new SimpleTUI();
|
|
382
|
+
tui.start();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Run if executed directly
|
|
386
|
+
if (require.main === module) {
|
|
387
|
+
main();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
module.exports = { SimpleTUI, main };
|
|
@@ -12,6 +12,7 @@ const { Installer } = require('../installers/core/installer');
|
|
|
12
12
|
const { IdeManager } = require('../installers/ide/manager');
|
|
13
13
|
const { displayLogo, displaySection, success, warning, error, info } = require('../lib/ui');
|
|
14
14
|
const { ErrorHandler } = require('../lib/error-handler');
|
|
15
|
+
const { IdeRegistry } = require('../lib/ide-registry');
|
|
15
16
|
|
|
16
17
|
const installer = new Installer();
|
|
17
18
|
const ideManager = new IdeManager();
|
|
@@ -209,11 +210,12 @@ async function handleSet(directory, status, manifestPath, key, value) {
|
|
|
209
210
|
|
|
210
211
|
case 'ides': {
|
|
211
212
|
const newIdes = value.split(',').map(ide => ide.trim());
|
|
212
|
-
const validIdes =
|
|
213
|
+
const validIdes = IdeRegistry.getAll();
|
|
213
214
|
|
|
214
215
|
// Validate IDEs
|
|
215
216
|
for (const ide of newIdes) {
|
|
216
|
-
|
|
217
|
+
const validation = IdeRegistry.validate(ide);
|
|
218
|
+
if (!validation.ok) {
|
|
217
219
|
handler.warning(
|
|
218
220
|
`Invalid IDE: ${ide}`,
|
|
219
221
|
`Valid IDEs: ${validIdes.join(', ')}`,
|
|
@@ -261,10 +263,10 @@ async function handleSet(directory, status, manifestPath, key, value) {
|
|
|
261
263
|
// Remove old IDE configs
|
|
262
264
|
for (const ide of oldIdes) {
|
|
263
265
|
if (!manifest.ides.includes(ide)) {
|
|
264
|
-
const configPath =
|
|
266
|
+
const configPath = IdeRegistry.getConfigPath(ide, directory);
|
|
265
267
|
if (await fs.pathExists(configPath)) {
|
|
266
268
|
await fs.remove(configPath);
|
|
267
|
-
info(`Removed ${
|
|
269
|
+
info(`Removed ${IdeRegistry.getDisplayName(ide)} configuration`);
|
|
268
270
|
}
|
|
269
271
|
}
|
|
270
272
|
}
|
|
@@ -272,7 +274,7 @@ async function handleSet(directory, status, manifestPath, key, value) {
|
|
|
272
274
|
// Add new IDE configs
|
|
273
275
|
for (const ide of manifest.ides) {
|
|
274
276
|
await ideManager.setup(ide, directory, status.path);
|
|
275
|
-
success(`Updated ${
|
|
277
|
+
success(`Updated ${IdeRegistry.getDisplayName(ide)} configuration`);
|
|
276
278
|
}
|
|
277
279
|
|
|
278
280
|
console.log();
|
|
@@ -281,29 +283,3 @@ async function handleSet(directory, status, manifestPath, key, value) {
|
|
|
281
283
|
|
|
282
284
|
console.log();
|
|
283
285
|
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Get IDE config path
|
|
287
|
-
*/
|
|
288
|
-
function getIdeConfigPath(projectDir, ide) {
|
|
289
|
-
const paths = {
|
|
290
|
-
'claude-code': '.claude/commands/agileflow',
|
|
291
|
-
cursor: '.cursor/rules/agileflow',
|
|
292
|
-
windsurf: '.windsurf/workflows/agileflow',
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
return path.join(projectDir, paths[ide] || '');
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Format IDE name for display
|
|
300
|
-
*/
|
|
301
|
-
function formatIdeName(ide) {
|
|
302
|
-
const names = {
|
|
303
|
-
'claude-code': 'Claude Code',
|
|
304
|
-
cursor: 'Cursor',
|
|
305
|
-
windsurf: 'Windsurf',
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
return names[ide] || ide;
|
|
309
|
-
}
|
|
@@ -28,6 +28,8 @@ const {
|
|
|
28
28
|
isRecoverable,
|
|
29
29
|
} = require('../../../lib/error-codes');
|
|
30
30
|
const { safeDump } = require('../../../lib/yaml-utils');
|
|
31
|
+
const { IdeRegistry } = require('../lib/ide-registry');
|
|
32
|
+
const { formatKeyValue, formatList, isTTY } = require('../../../lib/table-formatter');
|
|
31
33
|
|
|
32
34
|
const installer = new Installer();
|
|
33
35
|
|
|
@@ -239,8 +241,8 @@ module.exports = {
|
|
|
239
241
|
ideManager.setDocsFolder(status.docsFolder || 'docs');
|
|
240
242
|
|
|
241
243
|
for (const ide of status.ides) {
|
|
242
|
-
const configPath =
|
|
243
|
-
const ideName =
|
|
244
|
+
const configPath = IdeRegistry.getConfigPath(ide, directory);
|
|
245
|
+
const ideName = IdeRegistry.getDisplayName(ide);
|
|
244
246
|
|
|
245
247
|
if (await fs.pathExists(configPath)) {
|
|
246
248
|
// Count files in config
|
|
@@ -265,14 +267,14 @@ module.exports = {
|
|
|
265
267
|
|
|
266
268
|
// Check for orphaned configs
|
|
267
269
|
console.log(chalk.bold('\nOrphan Check:'));
|
|
268
|
-
const allIdes =
|
|
270
|
+
const allIdes = IdeRegistry.getAll();
|
|
269
271
|
let orphansFound = false;
|
|
270
272
|
|
|
271
273
|
for (const ide of allIdes) {
|
|
272
274
|
if (!status.ides || !status.ides.includes(ide)) {
|
|
273
|
-
const configPath =
|
|
275
|
+
const configPath = IdeRegistry.getConfigPath(ide, directory);
|
|
274
276
|
if (await fs.pathExists(configPath)) {
|
|
275
|
-
const ideName =
|
|
277
|
+
const ideName = IdeRegistry.getDisplayName(ide);
|
|
276
278
|
warning(`${ideName}: Config exists but not in manifest`);
|
|
277
279
|
orphansFound = true;
|
|
278
280
|
warnings++;
|
|
@@ -367,37 +369,6 @@ function compareVersions(a, b) {
|
|
|
367
369
|
return 0;
|
|
368
370
|
}
|
|
369
371
|
|
|
370
|
-
/**
|
|
371
|
-
* Get IDE config path
|
|
372
|
-
* @param {string} projectDir - Project directory
|
|
373
|
-
* @param {string} ide - IDE name
|
|
374
|
-
* @returns {string}
|
|
375
|
-
*/
|
|
376
|
-
function getIdeConfigPath(projectDir, ide) {
|
|
377
|
-
const paths = {
|
|
378
|
-
'claude-code': '.claude/commands/agileflow',
|
|
379
|
-
cursor: '.cursor/rules/agileflow',
|
|
380
|
-
windsurf: '.windsurf/workflows/agileflow',
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
return path.join(projectDir, paths[ide] || '');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Format IDE name for display
|
|
388
|
-
* @param {string} ide - IDE name
|
|
389
|
-
* @returns {string}
|
|
390
|
-
*/
|
|
391
|
-
function formatIdeName(ide) {
|
|
392
|
-
const names = {
|
|
393
|
-
'claude-code': 'Claude Code',
|
|
394
|
-
cursor: 'Cursor',
|
|
395
|
-
windsurf: 'Windsurf',
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
return names[ide] || ide;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
372
|
/**
|
|
402
373
|
* Count files in directory recursively
|
|
403
374
|
* @param {string} dirPath - Directory path
|
|
@@ -420,7 +391,7 @@ async function countFilesInDir(dirPath) {
|
|
|
420
391
|
}
|
|
421
392
|
|
|
422
393
|
/**
|
|
423
|
-
* Print summary
|
|
394
|
+
* Print summary using formatKeyValue for consistent output
|
|
424
395
|
* @param {number} issues - Issue count
|
|
425
396
|
* @param {number} warnings - Warning count
|
|
426
397
|
*/
|
|
@@ -430,9 +401,27 @@ function printSummary(issues, warnings) {
|
|
|
430
401
|
if (issues === 0 && warnings === 0) {
|
|
431
402
|
console.log(chalk.green.bold('No issues found.\n'));
|
|
432
403
|
} else if (issues === 0) {
|
|
433
|
-
console.log(
|
|
404
|
+
console.log(
|
|
405
|
+
formatKeyValue(
|
|
406
|
+
{
|
|
407
|
+
Warnings: chalk.yellow(warnings),
|
|
408
|
+
Issues: chalk.green('0'),
|
|
409
|
+
},
|
|
410
|
+
{ separator: ':', alignValues: false }
|
|
411
|
+
)
|
|
412
|
+
);
|
|
413
|
+
console.log();
|
|
434
414
|
} else {
|
|
435
|
-
console.log(
|
|
415
|
+
console.log(
|
|
416
|
+
formatKeyValue(
|
|
417
|
+
{
|
|
418
|
+
Issues: chalk.red(issues),
|
|
419
|
+
Warnings: chalk.yellow(warnings),
|
|
420
|
+
},
|
|
421
|
+
{ separator: ':', alignValues: false }
|
|
422
|
+
)
|
|
423
|
+
);
|
|
424
|
+
console.log();
|
|
436
425
|
}
|
|
437
426
|
}
|
|
438
427
|
|