@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,353 @@
1
+ import readline from 'readline';
2
+ import { readFileSync } from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { ProjectInitiator } from './init.js';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ /**
11
+ * AVC REPL - Interactive command-line interface
12
+ * Similar to Claude Code's interactive shell
13
+ */
14
+ export class AvcRepl {
15
+ constructor() {
16
+ this.running = false;
17
+ this.rl = null;
18
+ this.version = this.getVersion();
19
+ this.commands = [
20
+ { cmd: '/init', desc: 'Initialize an AVC project (Sponsor Call ceremony)' },
21
+ { cmd: '/status', desc: 'Show current project status' },
22
+ { cmd: '/help', desc: 'Show this help message' },
23
+ { cmd: '/version', desc: 'Show version information' },
24
+ { cmd: '/exit', desc: 'Exit AVC interactive mode' }
25
+ ];
26
+ this.lastInput = '';
27
+ this.commandListShown = false;
28
+ }
29
+
30
+ getVersion() {
31
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
32
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
33
+ return packageJson.version;
34
+ }
35
+
36
+ showBanner() {
37
+ console.log('\n');
38
+ console.log('AGILE VIBE CODING');
39
+ console.log('═════════════════');
40
+ console.log(`Version: ${this.version}`);
41
+ console.log('Framework for AI-powered Agile development\n');
42
+ }
43
+
44
+ showHelp() {
45
+ console.log('\n📚 Available Commands:\n');
46
+ this.commands.forEach(c => {
47
+ console.log(` ${c.cmd.padEnd(12)} ${c.desc}`);
48
+ });
49
+ console.log('\n');
50
+ }
51
+
52
+ showVersion() {
53
+ console.log(`\n🎯 AVC Framework v${this.version}`);
54
+ console.log(' Agile Vibe Coding - AI-powered development framework');
55
+ console.log(' https://agilevibecoding.org\n');
56
+ }
57
+
58
+ async handleCommand(command) {
59
+ const cmd = command.trim().toLowerCase();
60
+
61
+ switch (cmd) {
62
+ case '/help':
63
+ case '/h':
64
+ this.showHelp();
65
+ break;
66
+
67
+ case '/version':
68
+ case '/v':
69
+ this.showVersion();
70
+ break;
71
+
72
+ case '/exit':
73
+ case '/quit':
74
+ case '/q':
75
+ console.log('\n👋 Thanks for using AVC!\n');
76
+ this.stop();
77
+ break;
78
+
79
+ case '/init':
80
+ await this.runInit();
81
+ break;
82
+
83
+ case '/status':
84
+ await this.runStatus();
85
+ break;
86
+
87
+ case '':
88
+ // Empty command, just show prompt again
89
+ break;
90
+
91
+ default:
92
+ if (cmd.startsWith('/')) {
93
+ console.log(`\n❌ Unknown command: ${cmd}`);
94
+ console.log(' Type /help to see available commands\n');
95
+ } else {
96
+ console.log('\n💡 Commands must start with /');
97
+ console.log(' Example: /init, /status, /help\n');
98
+ }
99
+ break;
100
+ }
101
+ }
102
+
103
+ async runInit() {
104
+ console.log(''); // Empty line before init output
105
+ const initiator = new ProjectInitiator();
106
+ await initiator.init();
107
+ console.log(''); // Empty line after init output
108
+ }
109
+
110
+ async runStatus() {
111
+ console.log(''); // Empty line before status output
112
+ const initiator = new ProjectInitiator();
113
+ initiator.status();
114
+ console.log(''); // Empty line after status output
115
+ }
116
+
117
+ showPrompt() {
118
+ process.stdout.write('> ');
119
+ }
120
+
121
+ getTerminalWidth() {
122
+ // Try multiple ways to get terminal width
123
+ return process.stdout.columns ||
124
+ process.stderr.columns ||
125
+ (this.rl && this.rl.output && this.rl.output.columns) ||
126
+ 80;
127
+ }
128
+
129
+ showTopLine() {
130
+ const width = this.getTerminalWidth();
131
+ console.log('─'.repeat(width));
132
+ }
133
+
134
+ showBottomLine() {
135
+ const width = this.getTerminalWidth();
136
+ console.log('─'.repeat(width));
137
+ }
138
+
139
+ showInfoLine() {
140
+ console.log('Available: /init /status /help /version /exit | Type / to see commands');
141
+ }
142
+
143
+ clearCommandList() {
144
+ if (this.commandListShown) {
145
+ // Clear the command list lines (they're between input line and bottom line)
146
+ // Move down 1 to the first command line
147
+ readline.moveCursor(process.stdout, 0, 1);
148
+
149
+ // Clear each command line
150
+ this.commands.forEach(() => {
151
+ readline.clearLine(process.stdout, 0);
152
+ readline.moveCursor(process.stdout, 0, 1);
153
+ });
154
+
155
+ // Clear bottom line and redraw it
156
+ readline.clearLine(process.stdout, 0);
157
+ this.showBottomLine();
158
+
159
+ // Move cursor back up to input line (past commands + 1 for bottom line)
160
+ readline.moveCursor(process.stdout, 0, -(this.commands.length + 1));
161
+ readline.cursorTo(process.stdout, this.rl.cursor + 2); // +2 for "> "
162
+
163
+ this.commandListShown = false;
164
+ }
165
+ }
166
+
167
+ showCommandList(filter) {
168
+ // Clear any existing list first
169
+ this.clearCommandList();
170
+
171
+ // Don't show list if filter is empty or doesn't start with /
172
+ if (!filter || !filter.startsWith('/')) {
173
+ return;
174
+ }
175
+
176
+ // Filter commands
177
+ const filtered = this.commands.filter(c => c.cmd.startsWith(filter));
178
+
179
+ if (filtered.length === 0) {
180
+ return;
181
+ }
182
+
183
+ // Save cursor position
184
+ const cursorPos = this.rl.cursor;
185
+
186
+ // Move to line below input
187
+ readline.moveCursor(process.stdout, 0, 1);
188
+
189
+ // Clear the bottom line and write command list
190
+ readline.clearLine(process.stdout, 0);
191
+ filtered.forEach((c, i) => {
192
+ process.stdout.write(` ${c.cmd.padEnd(12)} ${c.desc}`);
193
+ if (i < filtered.length - 1) {
194
+ process.stdout.write('\n');
195
+ }
196
+ });
197
+
198
+ // Write bottom line after commands
199
+ process.stdout.write('\n');
200
+ this.showBottomLine();
201
+
202
+ // Move cursor back up to input position
203
+ readline.moveCursor(process.stdout, 0, -(filtered.length + 1));
204
+ readline.cursorTo(process.stdout, cursorPos + 2); // +2 for "> "
205
+
206
+ this.commandListShown = true;
207
+ }
208
+
209
+ updateCommandList() {
210
+ const currentInput = this.rl.line || '';
211
+
212
+ // Only update if input changed
213
+ if (currentInput !== this.lastInput) {
214
+ this.lastInput = currentInput;
215
+
216
+ if (currentInput.startsWith('/')) {
217
+ this.showCommandList(currentInput);
218
+ } else if (this.commandListShown) {
219
+ this.clearCommandList();
220
+ }
221
+ }
222
+ }
223
+
224
+ completer(line) {
225
+ // Return command names for TAB completion
226
+ if (!line.startsWith('/')) {
227
+ return [[], line];
228
+ }
229
+
230
+ const hits = this.commands
231
+ .filter((c) => c.cmd.startsWith(line))
232
+ .map(c => c.cmd);
233
+
234
+ return [hits.length ? hits : this.commands.map(c => c.cmd), line];
235
+ }
236
+
237
+ start() {
238
+ this.running = true;
239
+ this.showBanner();
240
+ this.showInfoLine();
241
+ console.log(''); // Empty line after info
242
+
243
+ this.rl = readline.createInterface({
244
+ input: process.stdin,
245
+ output: process.stdout,
246
+ prompt: '',
247
+ completer: (line) => this.completer(line),
248
+ tabSize: 4
249
+ });
250
+
251
+ // Enable keypress events for real-time command list
252
+ if (process.stdin.isTTY) {
253
+ readline.emitKeypressEvents(process.stdin);
254
+ process.stdin.setRawMode(true);
255
+ }
256
+
257
+ // Listen for terminal resize events
258
+ process.stdout.on('resize', () => {
259
+ // Terminal was resized - next prompt will use new width automatically
260
+ // since we read width dynamically in getTerminalWidth()
261
+ });
262
+
263
+ // Listen for keypress events
264
+ process.stdin.on('keypress', (str, key) => {
265
+ if (!this.running) return;
266
+
267
+ // Handle Ctrl+C
268
+ if (key && key.ctrl && key.name === 'c') {
269
+ this.clearCommandList();
270
+ if (process.stdin.isTTY) {
271
+ process.stdin.setRawMode(false);
272
+ }
273
+ console.log('\n\n👋 Thanks for using AVC!\n');
274
+ process.exit(0);
275
+ }
276
+
277
+ // Handle Enter - clear command list
278
+ if (key && key.name === 'return') {
279
+ this.clearCommandList();
280
+ return;
281
+ }
282
+
283
+ // Update command list after keystroke is processed
284
+ setImmediate(() => {
285
+ this.updateCommandList();
286
+ });
287
+ });
288
+
289
+ // Show initial prompt with bottom line
290
+ this.showTopLine();
291
+ this.showPrompt();
292
+ process.stdout.write('\n');
293
+ this.showBottomLine();
294
+
295
+ // Move cursor back up to input line
296
+ readline.moveCursor(process.stdout, 2, -1); // +2 for "> ", -1 line up
297
+
298
+ this.rl.on('line', async (line) => {
299
+ // Clear command list
300
+ this.clearCommandList();
301
+
302
+ // Move cursor to after bottom line
303
+ readline.moveCursor(process.stdout, 0, 1);
304
+ process.stdout.write('\n');
305
+
306
+ // Disable raw mode for command execution
307
+ if (process.stdin.isTTY) {
308
+ process.stdin.setRawMode(false);
309
+ }
310
+
311
+ // Process command (output appears here)
312
+ await this.handleCommand(line);
313
+
314
+ // Re-enable raw mode
315
+ if (process.stdin.isTTY && this.running) {
316
+ process.stdin.setRawMode(true);
317
+ }
318
+
319
+ // Show next prompt with bottom line
320
+ if (this.running) {
321
+ this.showTopLine();
322
+ this.showPrompt();
323
+ process.stdout.write('\n');
324
+ this.showBottomLine();
325
+
326
+ // Move cursor back up to input line
327
+ readline.moveCursor(process.stdout, 2, -1); // +2 for "> ", -1 line up
328
+ }
329
+ });
330
+
331
+ this.rl.on('close', () => {
332
+ if (process.stdin.isTTY) {
333
+ process.stdin.setRawMode(false);
334
+ }
335
+ if (this.running) {
336
+ console.log('\n👋 Thanks for using AVC!\n');
337
+ process.exit(0);
338
+ }
339
+ });
340
+ }
341
+
342
+ stop() {
343
+ this.running = false;
344
+ this.clearCommandList();
345
+ if (process.stdin.isTTY) {
346
+ process.stdin.setRawMode(false);
347
+ }
348
+ if (this.rl) {
349
+ this.rl.close();
350
+ }
351
+ process.exit(0);
352
+ }
353
+ }