vibecodingmachine-cli 2025.12.1-534 → 2025.12.6-1702

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/src/utils/auth.js CHANGED
@@ -31,7 +31,30 @@ class CLIAuth {
31
31
  * Check if authenticated (uses shared storage)
32
32
  */
33
33
  async isAuthenticated() {
34
- return await sharedAuth.isAuthenticated();
34
+ // First check if current token is valid
35
+ const isValid = await sharedAuth.isAuthenticated();
36
+ if (isValid) return true;
37
+
38
+ // If not valid, try to refresh
39
+ try {
40
+ const refreshToken = await sharedAuth.getRefreshToken();
41
+ if (refreshToken) {
42
+ // console.log(chalk.gray('Refreshing session...'));
43
+ const newTokens = await this._refreshTokens(refreshToken);
44
+
45
+ // Validate new ID token
46
+ await this._validateToken(newTokens.id_token);
47
+
48
+ // Save new tokens
49
+ await sharedAuth.saveToken(newTokens);
50
+ return true;
51
+ }
52
+ } catch (error) {
53
+ // Refresh failed (token expired or revoked), user needs to login again
54
+ // console.log(chalk.gray('Session refresh failed, please login again.'));
55
+ }
56
+
57
+ return false;
35
58
  }
36
59
 
37
60
  /**
@@ -82,7 +105,7 @@ class CLIAuth {
82
105
  res.on('end', () => {
83
106
  if (res.statusCode === 200) {
84
107
  const tokens = JSON.parse(data);
85
- resolve(tokens.id_token); // Return the ID token
108
+ resolve(tokens); // Return full tokens object
86
109
  } else {
87
110
  reject(new Error(`Token exchange failed: ${res.statusCode} - ${data}`));
88
111
  }
@@ -97,7 +120,53 @@ class CLIAuth {
97
120
  req.end();
98
121
  });
99
122
  }
123
+ /**
124
+ * Refresh tokens using refresh_token
125
+ */
126
+ async _refreshTokens(refreshToken) {
127
+ const https = require('https');
128
+
129
+ const tokenEndpoint = `https://${COGNITO_DOMAIN}/oauth2/token`;
130
+ const postData = new URLSearchParams({
131
+ grant_type: 'refresh_token',
132
+ client_id: CLIENT_ID,
133
+ refresh_token: refreshToken
134
+ }).toString();
135
+
136
+ return new Promise((resolve, reject) => {
137
+ const options = {
138
+ method: 'POST',
139
+ headers: {
140
+ 'Content-Type': 'application/x-www-form-urlencoded',
141
+ 'Content-Length': postData.length
142
+ }
143
+ };
100
144
 
145
+ const req = https.request(tokenEndpoint, options, (res) => {
146
+ let data = '';
147
+ res.on('data', (chunk) => { data += chunk; });
148
+ res.on('end', () => {
149
+ if (res.statusCode === 200) {
150
+ const tokens = JSON.parse(data);
151
+ // Cognito might not return a new refresh token, so we keep the old one if not provided
152
+ if (!tokens.refresh_token) {
153
+ tokens.refresh_token = refreshToken;
154
+ }
155
+ resolve(tokens);
156
+ } else {
157
+ reject(new Error(`Token refresh failed: ${res.statusCode} - ${data}`));
158
+ }
159
+ });
160
+ });
161
+
162
+ req.on('error', (error) => {
163
+ reject(new Error(`Token refresh request failed: ${error.message}`));
164
+ });
165
+
166
+ req.write(postData);
167
+ req.end();
168
+ });
169
+ }
101
170
  /**
102
171
  * Validate JWT token with signature verification
103
172
  * @param {string} idToken - JWT token to validate
@@ -312,18 +381,22 @@ class CLIAuth {
312
381
  try {
313
382
  // Exchange code for tokens
314
383
  console.log(chalk.gray('Exchanging authorization code for tokens...'));
315
- const idToken = await this._exchangeCodeForTokens(
384
+ // Exchange code for tokens
385
+ console.log(chalk.gray('Exchanging authorization code for tokens...'));
386
+ const tokens = await this._exchangeCodeForTokens(
316
387
  code,
317
388
  codeVerifier,
318
389
  `http://localhost:${PORT}/callback`
319
390
  );
320
391
 
392
+ const idToken = tokens.id_token;
393
+
321
394
  // Validate token (signature + claims)
322
395
  console.log(chalk.gray('Validating token...'));
323
396
  await this._validateToken(idToken);
324
397
 
325
398
  // Save token
326
- await sharedAuth.saveToken(idToken);
399
+ await sharedAuth.saveToken(tokens);
327
400
 
328
401
  // Show success page
329
402
  res.writeHead(200, {
@@ -428,7 +501,7 @@ class CLIAuth {
428
501
  serverClosed = true;
429
502
  server.close();
430
503
  console.log(chalk.green('✓ Authentication successful!\n'));
431
- resolve(idToken);
504
+ resolve(tokens.id_token);
432
505
  }
433
506
  } catch (error) {
434
507
  // Token exchange or validation failed
@@ -560,12 +633,16 @@ class CLIAuth {
560
633
 
561
634
  // Exchange code for tokens
562
635
  console.log(chalk.gray('Exchanging authorization code for tokens...'));
563
- const idToken = await this._exchangeCodeForTokens(
636
+ // Exchange code for tokens
637
+ console.log(chalk.gray('Exchanging authorization code for tokens...'));
638
+ const tokens = await this._exchangeCodeForTokens(
564
639
  code,
565
640
  codeVerifier,
566
641
  'http://localhost:3000/callback'
567
642
  );
568
643
 
644
+ const idToken = tokens.id_token;
645
+
569
646
  // Validate JWT token
570
647
  console.log(chalk.gray('Validating token...'));
571
648
  try {
@@ -577,7 +654,7 @@ class CLIAuth {
577
654
  }
578
655
 
579
656
  // Save token using shared storage (only if validation passed)
580
- await sharedAuth.saveToken(idToken);
657
+ await sharedAuth.saveToken(tokens);
581
658
 
582
659
  console.log(chalk.green('\n✓ Authentication successful!'));
583
660
  return idToken;
@@ -13,9 +13,6 @@ class AutoModeSimpleUI {
13
13
  this.chatCount = 0;
14
14
  this.maxChats = null;
15
15
  this.progress = 0;
16
- this.outputLineCount = 0; // Track output lines for periodic re-render
17
- this.lastCardPrintTime = Date.now(); // Track when we last printed the card
18
- this.lastCardPrintLine = 0; // Track line count when we last printed the card
19
16
 
20
17
  // Print header once
21
18
  console.log('\n' + chalk.bold.cyan('═══════════════════════════════════════════════════════════'));
@@ -109,30 +106,13 @@ class AutoModeSimpleUI {
109
106
  borderColor: 'magenta',
110
107
  backgroundColor: 'black'
111
108
  });
112
-
113
- // Print the card with a separator line before it for visibility
114
- if (this.outputLineCount > 0) {
115
- // Add separator when re-printing after output
116
- console.log(chalk.gray('─'.repeat(80)));
117
- }
109
+
118
110
  console.log(card);
119
- this.lastCardPrintTime = Date.now();
120
- this.lastCardPrintLine = this.outputLineCount;
121
111
  }
122
112
 
123
113
  appendOutput(line) {
124
- // Periodically re-print the workflow card to keep it visible
125
- // Re-print every 20 lines of output, or if more than 5 seconds have passed
126
- const linesSinceLastCard = this.outputLineCount - this.lastCardPrintLine;
127
- const timeSinceLastCard = Date.now() - this.lastCardPrintTime;
128
-
129
- if (linesSinceLastCard >= 20 || timeSinceLastCard > 5000) {
130
- this.printWorkflowCard();
131
- }
132
-
133
- // Increment output line counter
114
+ // Just print the line - no automatic card re-printing
134
115
  if (line && line.trim()) {
135
- this.outputLineCount++;
136
116
  console.log(line);
137
117
  }
138
118
  }
@@ -0,0 +1,293 @@
1
+ const inquirer = require('inquirer');
2
+ const chalk = require('chalk');
3
+ const boxen = require('boxen');
4
+ const fs = require('fs-extra');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const { getProviderDefinitions, saveProviderPreferences, getDefaultProviderOrder } = require('./provider-registry');
8
+ const { isKiroInstalled, installKiro } = require('./kiro-installer');
9
+
10
+ async function checkFirstRun() {
11
+ const configDir = path.join(os.homedir(), '.vibecodingmachine');
12
+
13
+ // If directory exists, we assume it's not the first run
14
+ if (await fs.pathExists(configDir)) {
15
+ return;
16
+ }
17
+
18
+ // FIRST RUN EXPERIENCE
19
+ console.clear();
20
+
21
+ // 1. Big Welcome Banner
22
+ console.log('\n' + boxen(
23
+ chalk.bold.cyan('Welcome to Vibe Coding Machine') + '\n\n' +
24
+ chalk.white('The future of coding is here.') + '\n' +
25
+ chalk.gray('Just describe what you want, and we code it for you.'),
26
+ {
27
+ padding: 1,
28
+ margin: 1,
29
+ borderStyle: 'double',
30
+ borderColor: 'cyan',
31
+ align: 'center'
32
+ }
33
+ ));
34
+
35
+ console.log(chalk.cyan('Let\'s get you set up with the best AI coding environment.\n'));
36
+ await new Promise(resolve => setTimeout(resolve, 1500));
37
+
38
+ // --- NEW: Vibe Coding Introduction ---
39
+ const { showIntro } = await inquirer.prompt([
40
+ {
41
+ type: 'confirm',
42
+ name: 'showIntro',
43
+ message: 'Are you new to Vibe Coding? Would you like a quick 30-second tour?',
44
+ default: true
45
+ }
46
+ ]);
47
+
48
+ if (showIntro) {
49
+ const introContent = [
50
+ chalk.bold.cyan('╔════════════════════════ Vibe Coding 101 ════════════════════════╗'),
51
+ chalk.cyan('║ ║'),
52
+ chalk.cyan('║ 1. What is an IDE? ║'),
53
+ chalk.cyan('║ Think of it like MS Word, but for writing code. ║'),
54
+ chalk.cyan('║ You need one to build apps, but don\'t worry—we\'ll install ║'),
55
+ chalk.cyan('║ the best ones for you automatically. ║'),
56
+ chalk.cyan('║ ║'),
57
+ chalk.cyan('║ 2. How it works ║'),
58
+ chalk.cyan('║ You simply describe the app or web page you want. ║'),
59
+ chalk.cyan('║ VibeCodingMachine then takes over your keyboard to write ║'),
60
+ chalk.cyan('║ the code inside the IDE (GUI mode) or runs in the ║'),
61
+ chalk.cyan('║ background (CLI mode) which is even faster. ║'),
62
+ chalk.cyan('║ ║'),
63
+ chalk.bold.cyan('╚═════════════════════════════════════════════════════════════════╝')
64
+ ].join('\n');
65
+
66
+ console.log('\n' + introContent + '\n');
67
+
68
+ await inquirer.prompt([{
69
+ type: 'input',
70
+ name: 'continue',
71
+ message: 'Press Enter to continue...'
72
+ }]);
73
+ }
74
+
75
+
76
+
77
+
78
+ // 2. Detect IDEs
79
+ const definitions = getProviderDefinitions();
80
+ const ideDefinitions = definitions.filter(d => d.type === 'ide');
81
+
82
+ const detectedIDEs = [];
83
+ const otherIDEs = [];
84
+
85
+ // Simple simulation of detection for standard apps on macOS
86
+ // In a real scenario, we might check other paths or registry on Windows
87
+ const platform = os.platform();
88
+ const getAppPath = (name) => platform === 'darwin' ? `/Applications/${name}.app` : null;
89
+
90
+ for (const ide of ideDefinitions) {
91
+ let installed = false;
92
+
93
+ if (ide.id === 'kiro') {
94
+ installed = isKiroInstalled();
95
+ } else if (ide.id === 'cursor') {
96
+ if (platform === 'darwin') installed = await fs.pathExists(getAppPath('Cursor'));
97
+ } else if (ide.id === 'windsurf') {
98
+ if (platform === 'darwin') installed = await fs.pathExists(getAppPath('Windsurf'));
99
+ } else if (ide.id === 'vscode') {
100
+ if (platform === 'darwin') installed = await fs.pathExists(getAppPath('Visual Studio Code'));
101
+ } else if (ide.id === 'antigravity') {
102
+ // Antigravity is just a provider here, maybe assume installed if running?
103
+ // Or check for some component. For now default to true or false based on something simple.
104
+ installed = true; // Assume available
105
+ }
106
+
107
+ if (installed) {
108
+ detectedIDEs.push(ide);
109
+ } else {
110
+ otherIDEs.push(ide);
111
+ }
112
+ }
113
+
114
+ // 3. Status Report & Selection
115
+ if (detectedIDEs.length > 0) {
116
+ console.log(chalk.green('\n✓ We found these IDEs and enabled them for you:'));
117
+ detectedIDEs.forEach(ide => {
118
+ console.log(chalk.gray(` • ${ide.name}`));
119
+ });
120
+ }
121
+
122
+ let selectedIDEs = [];
123
+
124
+ // Only prompt for uninstalled IDEs if there are any
125
+ if (otherIDEs.length > 0) {
126
+ console.log(); // Spacing
127
+ const choices = otherIDEs.map(ide => ({
128
+ name: ide.name,
129
+ value: ide.id,
130
+ checked: true // Select by default as requested
131
+ }));
132
+
133
+ const response = await inquirer.prompt([{
134
+ type: 'checkbox',
135
+ name: 'selectedIDEs',
136
+ message: 'Select additional IDEs to install & enable:',
137
+ choices: choices,
138
+ pageSize: 10
139
+ }]);
140
+ selectedIDEs = response.selectedIDEs;
141
+ }
142
+
143
+ // 4. Handle Installations
144
+ if (selectedIDEs.includes('kiro')) {
145
+ const kiroInstalled = isKiroInstalled();
146
+ if (!kiroInstalled) {
147
+ // installKiro logs its own inputs, but we want to handle failure gracefully
148
+ const success = await installKiro();
149
+ if (!success) {
150
+ console.log(chalk.yellow('\n⚠️ Kiro installation failed or was skipped.'));
151
+ console.log(chalk.gray('You can continue setting up other IDEs.'));
152
+ await new Promise(resolve => setTimeout(resolve, 2000));
153
+ } else {
154
+ // Post-install notification with auto-launch
155
+ console.log('\n' + boxen(
156
+ chalk.bold.yellow('⚠️ ACTION REQUIRED: SIGN IN ') + '\n\n' +
157
+ chalk.white('1. I will launch AWS Kiro IDE for you.') + '\n' +
158
+ chalk.white('2. Sign in with Google, GitHub, or AWS Builder ID.') + '\n' +
159
+ chalk.white('3. Click "Skip all" to skip survey questions.') + '\n' +
160
+ chalk.white('4. Once signed in, you can minimize or close the IDE.') + '\n\n' +
161
+ chalk.cyan('You must sign in for the CLI to control the IDE.'),
162
+ {
163
+ padding: 1,
164
+ margin: 1,
165
+ borderStyle: 'round',
166
+ borderColor: 'yellow'
167
+ }
168
+ ));
169
+
170
+ await inquirer.prompt([{
171
+ type: 'input',
172
+ name: 'launch',
173
+ message: 'Press Enter and I will launch Kiro for you to sign in to...'
174
+ }]);
175
+
176
+ const { exec } = require('child_process');
177
+ console.log(chalk.gray('Launching AWS Kiro...'));
178
+
179
+ try {
180
+ // Try to open AWS Kiro
181
+ exec('open -a "AWS Kiro"', (error) => {
182
+ if (error) {
183
+ // Fallback to just Kiro
184
+ exec('open -a "Kiro"', (err) => {
185
+ if (err) {
186
+ console.log(chalk.red('Could not auto-launch Kiro. Please open it manually.'));
187
+ }
188
+ });
189
+ }
190
+ });
191
+ } catch (e) {
192
+ // Ignore errors, user can launch manually
193
+ }
194
+
195
+ await inquirer.prompt([{
196
+ type: 'input',
197
+ name: 'continue',
198
+ message: 'Press Enter once you have signed in...'
199
+ }]);
200
+ }
201
+ }
202
+ }
203
+
204
+ // 5. Configure Preferences
205
+ // Enable detected IDEs AND selected IDEs, disable others
206
+ const defaultOrder = getDefaultProviderOrder();
207
+ const enabledMap = {};
208
+
209
+ defaultOrder.forEach(id => {
210
+ // Enable if it was detected OR selected
211
+ // For LLMs (not 'ide' type), keep enabled by default
212
+ const def = definitions.find(d => d.id === id);
213
+ if (def && def.type === 'ide') {
214
+ const isDetected = detectedIDEs.some(d => d.id === id);
215
+ const isSelected = selectedIDEs.includes(id);
216
+ enabledMap[id] = isDetected || isSelected;
217
+ } else {
218
+ enabledMap[id] = true; // Keep LLMs enabled by default
219
+ }
220
+ });
221
+
222
+ // Save initial preferences. provider-registry.js usually saves to ~/.config/vibecodingmachine/config.json
223
+ // But here we are checking ~/.vibecodingmachine.
224
+ // We should create the ~/.vibecodingmachine directory to mark first run as complete.
225
+ await fs.ensureDir(configDir);
226
+ await saveProviderPreferences(defaultOrder, enabledMap);
227
+
228
+ // --- NEW: CLI Usage Onboarding ---
229
+ // Moved here so IDEs are set up first
230
+ const { isFamiliar } = await inquirer.prompt([
231
+ {
232
+ type: 'confirm',
233
+ name: 'isFamiliar',
234
+ message: 'Are you familiar with how to use VibeCodingMachine CLI?',
235
+ default: false
236
+ }
237
+ ]);
238
+
239
+ if (!isFamiliar) {
240
+ const cliUsageContent = [
241
+ chalk.bold.green('╔════════════════════════ CLI Crash Course ═══════════════════════╗'),
242
+ chalk.green('║ ║'),
243
+ chalk.green('║ 1. Navigation ║'),
244
+ chalk.green('║ [↑/↓] Arrow keys to move selection ║'),
245
+ chalk.green('║ [Space] to toggle checkboxes [x] ║'),
246
+ chalk.green('║ [Enter] to confirm your choice ║'),
247
+ chalk.green('║ ║'),
248
+ chalk.green('║ 2. Key Features ║'),
249
+ chalk.green('║ • Auto Mode: The autonomous coding agent ║'),
250
+ chalk.green('║ • Direct Mode: Chat directly with LLMs ║'),
251
+ chalk.green('║ • IDE Setup: Install and configure tools ║'),
252
+ chalk.green('║ ║'),
253
+ chalk.green('║ 3. Pro Tip ║'),
254
+ chalk.green('║ If you ever get stuck, just restart the CLI with \'vcm\'. ║'),
255
+ chalk.green('║ ║'),
256
+ chalk.bold.green('╚═════════════════════════════════════════════════════════════════╝')
257
+ ].join('\n');
258
+
259
+ console.log('\n' + cliUsageContent + '\n');
260
+
261
+ await inquirer.prompt([{
262
+ type: 'input',
263
+ name: 'continue',
264
+ message: 'Press Enter to continue...'
265
+ }]);
266
+ }
267
+
268
+ // 6. Explanation
269
+ console.log('\n' + boxen(
270
+ chalk.bold.green(' Setup Complete! ') + '\n\n' +
271
+ chalk.white('Vibe Coding is about flow. You describe, we code.') + '\n' +
272
+ chalk.white('Using your local IDEs + Cloud LLMs for maximum power.'),
273
+ {
274
+ padding: 1,
275
+ margin: 1,
276
+ borderStyle: 'round',
277
+ borderColor: 'green'
278
+ }
279
+ ));
280
+
281
+ console.log(chalk.cyan('Press any key to continue to the main menu...'));
282
+
283
+ await new Promise((resolve) => {
284
+ process.stdin.setRawMode(true);
285
+ process.stdin.resume();
286
+ process.stdin.once('data', () => {
287
+ process.stdin.setRawMode(false);
288
+ resolve();
289
+ });
290
+ });
291
+ }
292
+
293
+ module.exports = { checkFirstRun };