@agile-vibe-coding/avc 0.1.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.
@@ -0,0 +1,764 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { render, Box, Text, useInput, useApp } from 'ink';
3
+ import SelectInput from 'ink-select-input';
4
+ import Spinner from 'ink-spinner';
5
+ import { execSync } from 'child_process';
6
+ import { readFileSync } from 'fs';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { ProjectInitiator } from './init.js';
10
+ import { UpdateChecker } from './update-checker.js';
11
+ import { UpdateInstaller } from './update-installer.js';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ // Get version from package.json (cached once at startup)
17
+ let _cachedVersion = null;
18
+ function getVersion() {
19
+ if (_cachedVersion) return _cachedVersion;
20
+ try {
21
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
22
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
23
+ _cachedVersion = packageJson.version;
24
+ } catch {
25
+ _cachedVersion = 'unknown';
26
+ }
27
+ return _cachedVersion;
28
+ }
29
+
30
+ // ASCII art letter definitions (4 chars wide, 6 rows tall; I is 3 wide)
31
+ const LOGO_LETTERS = {
32
+ 'A': [' ██ ', '█ █', '█ █', '████', '█ █', '█ █'],
33
+ 'G': [' ██ ', '█ ', '█ ', '█ ██', '█ █', ' ██ '],
34
+ 'I': ['███', ' █ ', ' █ ', ' █ ', ' █ ', '███'],
35
+ 'L': ['█ ', '█ ', '█ ', '█ ', '█ ', '████'],
36
+ 'E': ['████', '█ ', '███ ', '█ ', '█ ', '████'],
37
+ 'V': ['█ █', '█ █', '█ █', '█ █', ' ██ ', ' ██ '],
38
+ 'B': ['███ ', '█ █', '███ ', '█ █', '█ █', '███ '],
39
+ 'C': [' ███', '█ ', '█ ', '█ ', '█ ', ' ███'],
40
+ 'O': [' ██ ', '█ █', '█ █', '█ █', '█ █', ' ██ '],
41
+ 'D': ['███ ', '█ █', '█ █', '█ █', '█ █', '███ '],
42
+ 'N': ['█ █', '██ █', '█ ██', '█ █', '█ █', '█ █'],
43
+ };
44
+
45
+ // Gradient colors top to bottom
46
+ const LOGO_COLORS = ['#04e762', '#f5b700', '#dc0073', '#008bf8', '#dc0073', '#04e762'];
47
+
48
+ function renderLogo(text) {
49
+ const HEIGHT = 6;
50
+ const words = text.split(' ');
51
+ const lines = [];
52
+
53
+ for (let row = 0; row < HEIGHT; row++) {
54
+ const wordParts = words.map(word => {
55
+ return [...word].map(ch => {
56
+ const letter = LOGO_LETTERS[ch.toUpperCase()];
57
+ return letter ? letter[row] : ' ';
58
+ }).join(' ');
59
+ });
60
+ lines.push(wordParts.join(' '));
61
+ }
62
+
63
+ return lines;
64
+ }
65
+
66
+ // Banner component with ASCII art logo
67
+ const Banner = () => {
68
+ const version = getVersion();
69
+ const logoLines = renderLogo('AGILE VIBE CODING');
70
+
71
+ return React.createElement(Box, { flexDirection: 'column', marginBottom: 1 },
72
+ React.createElement(Text, null, ' '),
73
+ ...logoLines.map((line, i) =>
74
+ React.createElement(Text, { bold: true, color: LOGO_COLORS[i] }, ' ' + line)
75
+ ),
76
+ React.createElement(Text, null, ' '),
77
+ React.createElement(Text, null, ` v${version} │ AI-powered Agile development framework`),
78
+ React.createElement(Text, null, ' '),
79
+ React.createElement(Text, { bold: true, color: 'red' }, ' ⚠️ UNDER DEVELOPMENT - DO NOT USE ⚠️'),
80
+ React.createElement(Text, null, ' '),
81
+ React.createElement(Text, { dimColor: true }, ' Type / to see commands')
82
+ );
83
+ };
84
+
85
+ // Separator line component
86
+ const Separator = () => {
87
+ const [width, setWidth] = useState(process.stdout.columns || 80);
88
+
89
+ useEffect(() => {
90
+ const handleResize = () => {
91
+ setWidth(process.stdout.columns || 80);
92
+ };
93
+
94
+ process.stdout.on('resize', handleResize);
95
+ return () => process.stdout.off('resize', handleResize);
96
+ }, []);
97
+
98
+ return React.createElement(Text, null, '─'.repeat(width));
99
+ };
100
+
101
+ // Loading spinner component
102
+ const LoadingSpinner = ({ message }) => {
103
+ return React.createElement(Box, null,
104
+ React.createElement(Text, { color: 'green' },
105
+ React.createElement(Spinner, { type: 'dots' }),
106
+ ' ',
107
+ message
108
+ )
109
+ );
110
+ };
111
+
112
+ // Command selector component with number shortcuts
113
+ const CommandSelector = ({ onSelect, onCancel, filter }) => {
114
+ const allCommands = [
115
+ { label: '/init Initialize an AVC project (Sponsor Call ceremony)', value: '/init', key: '1' },
116
+ { label: '/status Show current project status', value: '/status', key: '2' },
117
+ { label: '/help Show this help message', value: '/help', key: '3' },
118
+ { label: '/version Show version information', value: '/version', key: '4' },
119
+ { label: '/exit Exit AVC interactive mode', value: '/exit', key: '5' }
120
+ ];
121
+
122
+ // Filter commands if filter is provided
123
+ const commands = filter
124
+ ? allCommands.filter(c => c.value.startsWith(filter.toLowerCase()))
125
+ : allCommands;
126
+
127
+ // Add number prefix to labels
128
+ const commandsWithNumbers = commands.map((cmd, idx) => ({
129
+ ...cmd,
130
+ label: `[${idx + 1}] ${cmd.label}`
131
+ }));
132
+
133
+ useInput((input, key) => {
134
+ if (key.escape) {
135
+ onCancel();
136
+ }
137
+ // Number shortcuts
138
+ const num = parseInt(input);
139
+ if (num >= 1 && num <= commands.length) {
140
+ onSelect(commands[num - 1]);
141
+ }
142
+ }, { isActive: true });
143
+
144
+ if (commands.length === 0) {
145
+ return React.createElement(Box, { flexDirection: 'column' },
146
+ React.createElement(Text, { color: 'yellow' }, 'No matching commands'),
147
+ React.createElement(Text, { dimColor: true }, '(Press Esc to cancel)')
148
+ );
149
+ }
150
+
151
+ return React.createElement(Box, { flexDirection: 'column' },
152
+ React.createElement(SelectInput, { items: commandsWithNumbers, onSelect: onSelect }),
153
+ React.createElement(Text, { dimColor: true }, '(Use arrows, number keys, or Esc to cancel)')
154
+ );
155
+ };
156
+
157
+ // Command history display
158
+ const HistoryHint = ({ hasHistory }) => {
159
+ if (!hasHistory) return null;
160
+
161
+ return React.createElement(Text, { dimColor: true, italic: true },
162
+ '(↑/↓ for history)'
163
+ );
164
+ };
165
+
166
+ // Input display with cursor
167
+ const InputWithCursor = ({ input }) => {
168
+ return React.createElement(Box, null,
169
+ React.createElement(Text, null, '> '),
170
+ React.createElement(Text, null, input),
171
+ React.createElement(Text, { inverse: true }, ' ')
172
+ );
173
+ };
174
+
175
+ // Bottom-right status display (version + update info)
176
+ const BottomRightStatus = () => {
177
+ const version = getVersion();
178
+ const [updateState, setUpdateState] = useState(null);
179
+ const [width, setWidth] = useState(process.stdout.columns || 80);
180
+
181
+ useEffect(() => {
182
+ const handleResize = () => {
183
+ setWidth(process.stdout.columns || 80);
184
+ };
185
+
186
+ process.stdout.on('resize', handleResize);
187
+ return () => process.stdout.off('resize', handleResize);
188
+ }, []);
189
+
190
+ // Poll update state every 2 seconds
191
+ useEffect(() => {
192
+ const checker = new UpdateChecker();
193
+
194
+ const updateStatus = () => {
195
+ const state = checker.readState();
196
+ setUpdateState(state);
197
+ };
198
+
199
+ updateStatus(); // Initial read
200
+ const interval = setInterval(updateStatus, 2000);
201
+
202
+ return () => clearInterval(interval);
203
+ }, []);
204
+
205
+ // Build status text
206
+ let statusText = `v${version}`;
207
+ let statusColor = 'gray';
208
+
209
+ if (updateState && updateState.updateAvailable && !updateState.userDismissed) {
210
+ if (updateState.updateReady) {
211
+ statusText = `v${version} → v${updateState.downloadedVersion} ready! (Ctrl+R)`;
212
+ statusColor = 'green';
213
+ } else if (updateState.updateStatus === 'downloading') {
214
+ statusText = `v${version} → v${updateState.latestVersion} downloading...`;
215
+ statusColor = 'blue';
216
+ } else if (updateState.updateStatus === 'failed') {
217
+ statusText = `v${version} | Update failed`;
218
+ statusColor = 'red';
219
+ } else if (updateState.updateStatus === 'pending' || updateState.updateStatus === 'idle') {
220
+ statusText = `v${version} → v${updateState.latestVersion} available`;
221
+ statusColor = 'yellow';
222
+ }
223
+ }
224
+
225
+ // Calculate padding to align to the right
226
+ const padding = Math.max(0, width - statusText.length - 2);
227
+
228
+ return React.createElement(Box, { justifyContent: 'flex-end' },
229
+ React.createElement(Text, { dimColor: statusColor === 'gray', color: statusColor === 'gray' ? undefined : statusColor },
230
+ ' '.repeat(padding),
231
+ statusText
232
+ )
233
+ );
234
+ };
235
+
236
+ // Main App component
237
+ const App = () => {
238
+ const { exit } = useApp();
239
+ const [mode, setMode] = useState('prompt'); // 'prompt' | 'selector' | 'executing'
240
+ const [input, setInput] = useState('');
241
+ const [output, setOutput] = useState('');
242
+ const [commandHistory, setCommandHistory] = useState([]);
243
+ const [historyIndex, setHistoryIndex] = useState(-1);
244
+ const [isExecuting, setIsExecuting] = useState(false);
245
+ const [executingMessage, setExecutingMessage] = useState('');
246
+
247
+ // Start update checker on mount
248
+ useEffect(() => {
249
+ const checker = new UpdateChecker();
250
+ const installer = new UpdateInstaller();
251
+
252
+ // Perform first update check immediately
253
+ checker.checkForUpdates().catch(() => {
254
+ // Silently fail
255
+ });
256
+
257
+ // Start background checker (checks every hour after first check)
258
+ checker.startBackgroundChecker();
259
+
260
+ // Auto-trigger update installation if available
261
+ setTimeout(() => {
262
+ installer.autoTriggerUpdate().catch(() => {
263
+ // Silently fail
264
+ });
265
+ }, 5000); // Wait 5 seconds after startup to avoid blocking
266
+ }, []);
267
+
268
+ // Available commands for Tab completion
269
+ const allCommands = [
270
+ '/init',
271
+ '/status',
272
+ '/help',
273
+ '/version',
274
+ '/exit'
275
+ ];
276
+
277
+ // Handle Tab key autocomplete
278
+ const handleTabComplete = () => {
279
+ // Only autocomplete if input starts with "/"
280
+ if (!input.startsWith('/')) return;
281
+
282
+ // Filter commands that match the current input
283
+ const matches = allCommands.filter(cmd =>
284
+ cmd.toLowerCase().startsWith(input.toLowerCase())
285
+ );
286
+
287
+ // If exactly one match, complete to that command
288
+ if (matches.length === 1) {
289
+ setInput(matches[0]);
290
+ // Show selector if not already shown
291
+ if (mode !== 'selector') {
292
+ setOutput('');
293
+ setMode('selector');
294
+ }
295
+ }
296
+ // If multiple matches, complete to common prefix
297
+ else if (matches.length > 1) {
298
+ // Find common prefix
299
+ let commonPrefix = matches[0];
300
+ for (let i = 1; i < matches.length; i++) {
301
+ let j = 0;
302
+ while (j < commonPrefix.length && j < matches[i].length &&
303
+ commonPrefix[j].toLowerCase() === matches[i][j].toLowerCase()) {
304
+ j++;
305
+ }
306
+ commonPrefix = commonPrefix.substring(0, j);
307
+ }
308
+
309
+ // If common prefix is longer than current input, use it
310
+ if (commonPrefix.length > input.length) {
311
+ setInput(commonPrefix);
312
+ }
313
+
314
+ // Show selector if not already shown
315
+ if (mode !== 'selector') {
316
+ setOutput('');
317
+ setMode('selector');
318
+ }
319
+ }
320
+ };
321
+
322
+ // Command aliases
323
+ const resolveAlias = (cmd) => {
324
+ const aliases = {
325
+ '/h': '/help',
326
+ '/v': '/version',
327
+ '/q': '/exit',
328
+ '/quit': '/exit',
329
+ '/i': '/init',
330
+ '/s': '/status'
331
+ };
332
+ return aliases[cmd.toLowerCase()] || cmd;
333
+ };
334
+
335
+ // Handle command execution
336
+ const executeCommand = async (cmd) => {
337
+ const command = resolveAlias(cmd.trim());
338
+
339
+ if (!command) {
340
+ setMode('prompt');
341
+ setInput('');
342
+ return;
343
+ }
344
+
345
+ // Add to history (avoid duplicates of last command)
346
+ if (command && (commandHistory.length === 0 || commandHistory[commandHistory.length - 1] !== command)) {
347
+ setCommandHistory([...commandHistory, command]);
348
+ }
349
+ setHistoryIndex(-1);
350
+
351
+ setMode('executing');
352
+ setIsExecuting(true);
353
+ setOutput(''); // Clear previous output
354
+
355
+ try {
356
+ switch (command.toLowerCase()) {
357
+ case '/help':
358
+ setExecutingMessage('Loading help...');
359
+ setOutput(showHelp());
360
+ break;
361
+
362
+ case '/version':
363
+ setExecutingMessage('Loading version info...');
364
+ setOutput(showVersion());
365
+ break;
366
+
367
+ case '/exit':
368
+ setIsExecuting(false);
369
+ setOutput('\n👋 Thanks for using AVC!\n');
370
+ setTimeout(() => {
371
+ exit();
372
+ process.exit(0);
373
+ }, 500);
374
+ return;
375
+
376
+ case '/init':
377
+ setExecutingMessage('Initializing project...');
378
+ await runInit();
379
+ break;
380
+
381
+ case '/status':
382
+ setExecutingMessage('Checking project status...');
383
+ await runStatus();
384
+ break;
385
+
386
+ case '/restart':
387
+ setIsExecuting(false);
388
+ setOutput('\n🔄 Restarting AVC...\n');
389
+ setTimeout(() => {
390
+ exit();
391
+ try {
392
+ execSync(process.argv.join(' '), { stdio: 'inherit' });
393
+ } catch { }
394
+ process.exit(0);
395
+ }, 500);
396
+ return;
397
+
398
+ default:
399
+ if (command.startsWith('/')) {
400
+ setOutput(`\n❌ Unknown command: ${command}\n Type /help to see available commands\n Tip: Try /h for help, /v for version, /q to exit\n`);
401
+ } else {
402
+ setOutput(`\n💡 Commands must start with /\n Example: /init, /status, /help\n Tip: Type / and press Enter to see all commands\n`);
403
+ }
404
+ }
405
+ } catch (error) {
406
+ setOutput(`\n❌ Error: ${error.message}\n`);
407
+ }
408
+
409
+ // Return to prompt mode
410
+ setIsExecuting(false);
411
+ setTimeout(() => {
412
+ setMode('prompt');
413
+ setInput('');
414
+ }, 100);
415
+ };
416
+
417
+ const showHelp = () => {
418
+ return `
419
+ 📚 Available Commands:
420
+
421
+ /init (or /i) Initialize an AVC project (Sponsor Call ceremony)
422
+ /status (or /s) Show current project status
423
+ /help (or /h) Show this help message
424
+ /version (or /v) Show version information
425
+ /restart Restart AVC (Ctrl+R)
426
+ /exit (or /q) Exit AVC interactive mode
427
+
428
+ 💡 Tips:
429
+ - Type / and press Enter to see interactive command selector
430
+ - Use arrow keys (↑/↓) to navigate command history
431
+ - Use Tab key to auto-complete commands
432
+ - Use number keys (1-5) to quickly select commands from the menu
433
+ - Press Esc to cancel command selector or dismiss notifications
434
+ - Press Ctrl+R to restart after updates
435
+ `;
436
+ };
437
+
438
+ const showVersion = () => {
439
+ const version = getVersion();
440
+ return `
441
+ 🎯 AVC Framework v${version}
442
+ Agile Vibe Coding - AI-powered development framework
443
+ https://agilevibecoding.org
444
+ `;
445
+ };
446
+
447
+ const runInit = async () => {
448
+ setOutput('\n'); // Empty line before init output
449
+ const initiator = new ProjectInitiator();
450
+
451
+ // Capture console.log output
452
+ const originalLog = console.log;
453
+ let logs = [];
454
+ console.log = (...args) => {
455
+ logs.push(args.join(' '));
456
+ };
457
+
458
+ try {
459
+ await initiator.init();
460
+ } finally {
461
+ console.log = originalLog;
462
+ }
463
+
464
+ setOutput(logs.join('\n') + '\n');
465
+ };
466
+
467
+ const runStatus = async () => {
468
+ setOutput('\n'); // Empty line before status output
469
+ const initiator = new ProjectInitiator();
470
+
471
+ // Capture console.log output
472
+ const originalLog = console.log;
473
+ let logs = [];
474
+ console.log = (...args) => {
475
+ logs.push(args.join(' '));
476
+ };
477
+
478
+ try {
479
+ initiator.status();
480
+ } finally {
481
+ console.log = originalLog;
482
+ }
483
+
484
+ setOutput(logs.join('\n') + '\n');
485
+ };
486
+
487
+ // Handle keyboard input in prompt mode
488
+ useInput((inputChar, key) => {
489
+ if (mode !== 'prompt') return;
490
+
491
+ // Handle Ctrl+R for restart (re-exec current process)
492
+ if (key.ctrl && inputChar === 'r') {
493
+ setOutput('\n🔄 Restarting AVC...\n');
494
+ setTimeout(() => {
495
+ exit();
496
+ try {
497
+ execSync(process.argv.join(' '), { stdio: 'inherit' });
498
+ } catch { }
499
+ process.exit(0);
500
+ }, 500);
501
+ return;
502
+ }
503
+
504
+ // Handle Esc to dismiss update notification
505
+ if (key.escape) {
506
+ const checker = new UpdateChecker();
507
+ const state = checker.readState();
508
+ if (state.updateAvailable && !state.userDismissed) {
509
+ state.userDismissed = true;
510
+ state.dismissedAt = new Date().toISOString();
511
+ checker.writeState(state);
512
+ }
513
+ return;
514
+ }
515
+
516
+ // Handle up/down arrows for history
517
+ if (key.upArrow && commandHistory.length > 0) {
518
+ const newIndex = historyIndex === -1
519
+ ? commandHistory.length - 1
520
+ : Math.max(0, historyIndex - 1);
521
+ setHistoryIndex(newIndex);
522
+ setInput(commandHistory[newIndex]);
523
+ return;
524
+ }
525
+
526
+ if (key.downArrow && historyIndex !== -1) {
527
+ const newIndex = historyIndex + 1;
528
+ if (newIndex >= commandHistory.length) {
529
+ setHistoryIndex(-1);
530
+ setInput('');
531
+ } else {
532
+ setHistoryIndex(newIndex);
533
+ setInput(commandHistory[newIndex]);
534
+ }
535
+ return;
536
+ }
537
+
538
+ // Handle Tab key for autocomplete
539
+ if (key.tab) {
540
+ handleTabComplete();
541
+ return;
542
+ }
543
+
544
+ // Handle Enter key
545
+ if (key.return) {
546
+ if (input === '/' || input.startsWith('/')) {
547
+ // If just "/" or partial command, show/stay in selector
548
+ if (input === '/') {
549
+ setOutput(''); // Clear previous output
550
+ setMode('selector');
551
+ setInput(''); // Clear input when entering selector
552
+ } else {
553
+ // Execute the typed command or selected command
554
+ executeCommand(input);
555
+ }
556
+ } else {
557
+ // Execute command
558
+ executeCommand(input);
559
+ }
560
+ return;
561
+ }
562
+
563
+ // Handle backspace
564
+ if (key.backspace || key.delete) {
565
+ const newInput = input.slice(0, -1);
566
+ setInput(newInput);
567
+ setHistoryIndex(-1);
568
+
569
+ // If we're in selector mode and user deletes the "/", exit selector
570
+ if (mode === 'selector' && !newInput.startsWith('/')) {
571
+ setMode('prompt');
572
+ }
573
+ return;
574
+ }
575
+
576
+ // Handle character input
577
+ if (inputChar) {
578
+ const newInput = input + inputChar;
579
+ setInput(newInput);
580
+ setHistoryIndex(-1);
581
+
582
+ // Show selector immediately when "/" is typed
583
+ if (newInput === '/' || (newInput.startsWith('/') && newInput.length > 1)) {
584
+ setOutput(''); // Clear previous output
585
+ setMode('selector');
586
+ }
587
+ }
588
+ }, { isActive: mode === 'prompt' });
589
+
590
+ // Handle keyboard input in selector mode
591
+ useInput((inputChar, key) => {
592
+ if (mode !== 'selector') return;
593
+
594
+ // Handle Tab key for autocomplete
595
+ if (key.tab) {
596
+ handleTabComplete();
597
+ return;
598
+ }
599
+
600
+ // Handle backspace
601
+ if (key.backspace || key.delete) {
602
+ const newInput = input.slice(0, -1);
603
+ setInput(newInput);
604
+
605
+ // If user deletes the "/", exit selector
606
+ if (!newInput.startsWith('/')) {
607
+ setMode('prompt');
608
+ }
609
+ return;
610
+ }
611
+
612
+ // Handle character input (typing continues to filter)
613
+ if (inputChar && !key.ctrl && !key.meta) {
614
+ const newInput = input + inputChar;
615
+ setInput(newInput);
616
+ }
617
+ }, { isActive: mode === 'selector' });
618
+
619
+ // Render output - show spinner during execution, or output after completion
620
+ const renderOutput = () => {
621
+ // Show spinner while executing
622
+ if (isExecuting) {
623
+ return React.createElement(React.Fragment, null,
624
+ React.createElement(Separator),
625
+ React.createElement(Box, { marginY: 1 },
626
+ React.createElement(LoadingSpinner, { message: executingMessage })
627
+ )
628
+ );
629
+ }
630
+
631
+ // Show output if available (even after returning to prompt mode)
632
+ if (output) {
633
+ return React.createElement(React.Fragment, null,
634
+ React.createElement(Separator),
635
+ React.createElement(Box, { marginY: 1 },
636
+ React.createElement(Text, null, output)
637
+ )
638
+ );
639
+ }
640
+
641
+ return null;
642
+ };
643
+
644
+ // Render selector when in selector mode
645
+ const renderSelector = () => {
646
+ if (mode !== 'selector') return null;
647
+
648
+ return React.createElement(React.Fragment, null,
649
+ React.createElement(Separator),
650
+ React.createElement(Box, { flexDirection: 'column', marginY: 1 },
651
+ React.createElement(Box, { marginBottom: 1 },
652
+ React.createElement(InputWithCursor, { input: input })
653
+ ),
654
+ React.createElement(CommandSelector, {
655
+ filter: input,
656
+ onSelect: (item) => {
657
+ executeCommand(item.value);
658
+ },
659
+ onCancel: () => {
660
+ setMode('prompt');
661
+ setInput('');
662
+ }
663
+ })
664
+ )
665
+ );
666
+ };
667
+
668
+ // Render prompt when in prompt mode
669
+ const renderPrompt = () => {
670
+ if (mode !== 'prompt') return null;
671
+
672
+ return React.createElement(React.Fragment, null,
673
+ React.createElement(Separator),
674
+ React.createElement(Box, { flexDirection: 'column' },
675
+ React.createElement(InputWithCursor, { input: input }),
676
+ React.createElement(HistoryHint, { hasHistory: commandHistory.length > 0 })
677
+ ),
678
+ React.createElement(Separator)
679
+ );
680
+ };
681
+
682
+ return React.createElement(Box, { flexDirection: 'column' },
683
+ React.createElement(Banner),
684
+ renderOutput(),
685
+ renderSelector(),
686
+ renderPrompt(),
687
+ React.createElement(BottomRightStatus)
688
+ );
689
+ };
690
+
691
+ // Export render function
692
+ export function startRepl() {
693
+ console.clear();
694
+ render(React.createElement(App));
695
+ }
696
+
697
+ // Export non-interactive command execution function
698
+ export async function executeCommand(cmd) {
699
+ const aliases = {
700
+ '/h': '/help',
701
+ '/v': '/version',
702
+ '/q': '/exit',
703
+ '/quit': '/exit',
704
+ '/i': '/init',
705
+ '/s': '/status'
706
+ };
707
+
708
+ const command = (aliases[cmd.toLowerCase()] || cmd).toLowerCase();
709
+
710
+ try {
711
+ switch (command) {
712
+ case '/help':
713
+ console.log(`
714
+ 📚 Available Commands:
715
+
716
+ /init (or /i) Initialize an AVC project (Sponsor Call ceremony)
717
+ /status (or /s) Show current project status
718
+ /help (or /h) Show this help message
719
+ /version (or /v) Show version information
720
+ /exit (or /q) Exit AVC interactive mode
721
+
722
+ 💡 Tips:
723
+ - Run 'avc' without arguments to start interactive mode
724
+ - Use Tab key to auto-complete commands
725
+ - Use arrow keys (↑/↓) to navigate command history
726
+ `);
727
+ process.exit(0);
728
+
729
+ case '/version':
730
+ const version = getVersion();
731
+ console.log(`
732
+ 🎯 AVC Framework v${version}
733
+ Agile Vibe Coding - AI-powered development framework
734
+ https://agilevibecoding.org
735
+ `);
736
+ process.exit(0);
737
+
738
+ case '/exit':
739
+ console.log('\n👋 Thanks for using AVC!\n');
740
+ process.exit(0);
741
+
742
+ case '/init':
743
+ const initiator = new ProjectInitiator();
744
+ await initiator.init();
745
+ process.exit(0);
746
+
747
+ case '/status':
748
+ const statusInitiator = new ProjectInitiator();
749
+ statusInitiator.status();
750
+ process.exit(0);
751
+
752
+ default:
753
+ if (command.startsWith('/')) {
754
+ console.error(`\n❌ Unknown command: ${command}\n Type 'avc help' to see available commands\n`);
755
+ } else {
756
+ console.error(`\n💡 Commands must start with /\n Example: avc init, avc status, avc help\n`);
757
+ }
758
+ process.exit(1);
759
+ }
760
+ } catch (error) {
761
+ console.error(`\n❌ Error: ${error.message}\n`);
762
+ process.exit(1);
763
+ }
764
+ }