vibecodingmachine-cli 2025.12.25-1541 → 2026.1.3-2209

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,123 @@
1
+ const chalk = require('chalk');
2
+ const { getRepoPath } = require('../utils/config');
3
+ const {
4
+ createRequirementBranch,
5
+ mergeRequirementBranch,
6
+ removeRequirementFeature,
7
+ listRequirementBranches,
8
+ getCurrentBranch
9
+ } = require('vibecodingmachine-core');
10
+
11
+ async function start(requirementTitle) {
12
+ try {
13
+ const repoPath = await getRepoPath();
14
+ if (!repoPath) {
15
+ console.log(chalk.yellow('No repository configured'));
16
+ console.log(chalk.gray('Run'), chalk.cyan('vcm repo:set <path>'));
17
+ process.exit(1);
18
+ }
19
+
20
+ console.log(chalk.cyan(`Creating feature branch for: ${requirementTitle}`));
21
+ const result = await createRequirementBranch(repoPath, requirementTitle);
22
+
23
+ if (result.success) {
24
+ if (result.alreadyExists) {
25
+ console.log(chalk.yellow(`✓ Switched to existing branch: ${result.branchName}`));
26
+ } else {
27
+ console.log(chalk.green(`✓ Created and switched to branch: ${result.branchName}`));
28
+ }
29
+ console.log(chalk.gray(` Parent branch: ${result.parentBranch}`));
30
+ } else {
31
+ console.log(chalk.red(`✗ Failed to create branch: ${result.error}`));
32
+ process.exit(1);
33
+ }
34
+ } catch (error) {
35
+ console.error(chalk.red('Error creating feature branch:'), error.message);
36
+ process.exit(1);
37
+ }
38
+ }
39
+
40
+ async function finish(requirementTitle, options = {}) {
41
+ try {
42
+ const repoPath = await getRepoPath();
43
+ if (!repoPath) {
44
+ console.log(chalk.yellow('No repository configured'));
45
+ process.exit(1);
46
+ }
47
+
48
+ const parentBranch = options.parent || 'main';
49
+
50
+ console.log(chalk.cyan(`Merging feature branch for: ${requirementTitle}`));
51
+ const result = await mergeRequirementBranch(repoPath, requirementTitle, parentBranch);
52
+
53
+ if (result.success) {
54
+ console.log(chalk.green(`✓ Feature merged and branch deleted`));
55
+ } else {
56
+ console.log(chalk.red(`✗ Failed to merge branch: ${result.error}`));
57
+ process.exit(1);
58
+ }
59
+ } catch (error) {
60
+ console.error(chalk.red('Error merging feature branch:'), error.message);
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ async function remove(requirementTitle) {
66
+ try {
67
+ const repoPath = await getRepoPath();
68
+ if (!repoPath) {
69
+ console.log(chalk.yellow('No repository configured'));
70
+ process.exit(1);
71
+ }
72
+
73
+ console.log(chalk.cyan(`Removing feature for: ${requirementTitle}`));
74
+ const result = await removeRequirementFeature(repoPath, requirementTitle);
75
+
76
+ if (result.success) {
77
+ console.log(chalk.green(`✓ Feature removed (merge reverted)`));
78
+ } else {
79
+ console.log(chalk.red(`✗ Failed to remove feature: ${result.error}`));
80
+ process.exit(1);
81
+ }
82
+ } catch (error) {
83
+ console.error(chalk.red('Error removing feature:'), error.message);
84
+ process.exit(1);
85
+ }
86
+ }
87
+
88
+ async function list() {
89
+ try {
90
+ const repoPath = await getRepoPath();
91
+ if (!repoPath) {
92
+ console.log(chalk.yellow('No repository configured'));
93
+ process.exit(1);
94
+ }
95
+
96
+ const branches = listRequirementBranches(repoPath);
97
+ const currentBranch = getCurrentBranch(repoPath);
98
+
99
+ if (branches.length === 0) {
100
+ console.log(chalk.yellow('No requirement branches found'));
101
+ return;
102
+ }
103
+
104
+ console.log(chalk.cyan('\nRequirement Branches:\n'));
105
+ branches.forEach(({ branchName, reqNumber }) => {
106
+ const isCurrent = branchName === currentBranch;
107
+ const marker = isCurrent ? chalk.green('* ') : ' ';
108
+ const color = isCurrent ? chalk.green : chalk.white;
109
+ console.log(`${marker}${color(branchName)}`);
110
+ });
111
+ console.log();
112
+ } catch (error) {
113
+ console.error(chalk.red('Error listing branches:'), error.message);
114
+ process.exit(1);
115
+ }
116
+ }
117
+
118
+ module.exports = {
119
+ start,
120
+ finish,
121
+ remove,
122
+ list
123
+ };
@@ -43,34 +43,39 @@ async function getRepo() {
43
43
  }
44
44
  }
45
45
 
46
- async function initRepo() {
46
+ async function initRepo(options = {}) {
47
47
  try {
48
48
  const cwd = process.cwd();
49
49
  const repoName = path.basename(cwd);
50
50
  const insideDir = path.join(cwd, '.vibecodingmachine');
51
51
  const siblingDir = path.join(path.dirname(cwd), `.vibecodingmachine-${repoName}`);
52
52
 
53
- // Ask user where to create the directory
54
- const { location } = await inquirer.prompt([
55
- {
56
- type: 'list',
57
- name: 'location',
58
- message: 'Where would you like to create the VibeCodingMachine directory?',
59
- choices: [
60
- {
61
- name: `Inside this repository (${chalk.cyan('.vibecodingmachine')}) - suggested`,
62
- value: 'inside',
63
- short: 'Inside repository'
64
- },
65
- {
66
- name: `As a sibling directory (${chalk.cyan(`../.vibecodingmachine-${repoName}`)}) - keeps configs separate from code`,
67
- value: 'sibling',
68
- short: 'Sibling directory'
69
- }
70
- ],
71
- default: 'inside'
72
- }
73
- ]);
53
+ let location = options.location;
54
+
55
+ if (location !== 'inside' && location !== 'sibling') {
56
+ // Ask user where to create the directory
57
+ const answer = await inquirer.prompt([
58
+ {
59
+ type: 'list',
60
+ name: 'location',
61
+ message: 'Where would you like to create the VibeCodingMachine directory?',
62
+ choices: [
63
+ {
64
+ name: `Inside this repository (${chalk.cyan('.vibecodingmachine')}) - suggested`,
65
+ value: 'inside',
66
+ short: 'Inside repository'
67
+ },
68
+ {
69
+ name: `As a sibling directory (${chalk.cyan(`../.vibecodingmachine-${repoName}`)}) - keeps configs separate from code`,
70
+ value: 'sibling',
71
+ short: 'Sibling directory'
72
+ }
73
+ ],
74
+ default: 'inside'
75
+ }
76
+ ]);
77
+ location = answer.location;
78
+ }
74
79
 
75
80
  const allnightDir = location === 'inside' ? insideDir : siblingDir;
76
81
 
@@ -2,13 +2,18 @@ const chalk = require('chalk');
2
2
  const fs = require('fs-extra');
3
3
  const path = require('path');
4
4
  const inquirer = require('inquirer');
5
- const { t } = require('vibecodingmachine-core');
5
+ const { t, errorReporter } = require('vibecodingmachine-core');
6
6
 
7
7
  /**
8
8
  * List requirements for a specific computer
9
9
  */
10
10
  async function listRemoteRequirements(computerId) {
11
11
  try {
12
+ // Validate input
13
+ if (!computerId || typeof computerId !== 'string') {
14
+ throw new TypeError('computerId must be a non-empty string');
15
+ }
16
+
12
17
  const repoPath = process.cwd();
13
18
  const reqFilename = `REQUIREMENTS-${computerId}.md`;
14
19
  const reqPath = path.join(repoPath, '.vibecodingmachine', reqFilename);
@@ -54,6 +59,10 @@ async function listRemoteRequirements(computerId) {
54
59
 
55
60
  } catch (error) {
56
61
  console.error(chalk.red('\n✗ Failed to list requirements:'), error.message);
62
+ await errorReporter.reportError(error, {
63
+ command: 'listRemoteRequirements',
64
+ computerId
65
+ });
57
66
  throw error;
58
67
  }
59
68
  }
@@ -63,6 +72,15 @@ async function listRemoteRequirements(computerId) {
63
72
  */
64
73
  async function addRemoteRequirement(computerId, requirementText) {
65
74
  try {
75
+ // Validate inputs
76
+ if (!computerId || typeof computerId !== 'string') {
77
+ throw new TypeError('computerId must be a non-empty string');
78
+ }
79
+
80
+ if (!requirementText || typeof requirementText !== 'string') {
81
+ throw new TypeError('requirementText must be a non-empty string');
82
+ }
83
+
66
84
  const repoPath = process.cwd();
67
85
  const reqFilename = `REQUIREMENTS-${computerId}.md`;
68
86
  const reqPath = path.join(repoPath, '.vibecodingmachine', reqFilename);
@@ -144,6 +162,11 @@ DONE
144
162
 
145
163
  } catch (error) {
146
164
  console.error(chalk.red('\n✗ Failed to add requirement:'), error.message);
165
+ await errorReporter.reportError(error, {
166
+ command: 'addRemoteRequirement',
167
+ computerId,
168
+ requirementText
169
+ });
147
170
  throw error;
148
171
  }
149
172
  }
@@ -101,17 +101,113 @@ async function getReqPathOrExit() {
101
101
 
102
102
  async function list(options) {
103
103
  try {
104
- const { reqPath } = await getReqPathOrExit();
105
- if (!await fs.pathExists(reqPath)) {
104
+ const { repoPath, reqPath } = await getReqPathOrExit();
105
+
106
+ // Handle --all-computers flag
107
+ if (options && options.allComputers) {
108
+ const vibeDir = path.join(repoPath, '.vibecodingmachine');
109
+ if (!await fs.pathExists(vibeDir)) {
110
+ console.log(chalk.yellow('No .vibecodingmachine directory found.'));
111
+ return;
112
+ }
113
+
114
+ // Find all REQUIREMENTS-*.md files
115
+ const files = await fs.readdir(vibeDir);
116
+ const reqFiles = files.filter(f => f.startsWith('REQUIREMENTS') && f.endsWith('.md'));
117
+
118
+ if (reqFiles.length === 0) {
119
+ console.log(chalk.yellow('No requirements files found.'));
120
+ return;
121
+ }
122
+
123
+ // Extract computer info and requirements from each file
124
+ for (const file of reqFiles) {
125
+ const filePath = path.join(vibeDir, file);
126
+ const content = await fs.readFile(filePath, 'utf8');
127
+ const lines = content.split('\n');
128
+
129
+ // Extract hostname from file
130
+ let hostname = 'Unknown';
131
+ const hostnameMatch = content.match(/- \*\*Hostname\*\*:\s*(.+)/);
132
+ if (hostnameMatch) {
133
+ hostname = hostnameMatch[1].trim();
134
+ }
135
+
136
+ // Extract focus area
137
+ let focus = '';
138
+ const focusMatch = content.match(/## 🎯 Focus\n\*\*(.+)\*\*/);
139
+ if (focusMatch) {
140
+ focus = focusMatch[1].trim();
141
+ }
142
+
143
+ // Filter by focus if specified
144
+ if (options.focus && focus && !focus.toLowerCase().includes(options.focus.toLowerCase())) {
145
+ continue;
146
+ }
147
+
148
+ console.log(chalk.blue.bold(`\n📍 ${hostname}`) + (focus ? chalk.gray(` (${focus})`) : ''));
149
+
150
+ // List requirements from this file
151
+ for (const line of lines) {
152
+ if (line.startsWith('### ') || line.startsWith('## ')) {
153
+ const statusFilter = options.status ? String(options.status).toLowerCase() : null;
154
+ if (!statusFilter || line.toLowerCase().includes(statusFilter)) {
155
+ console.log(' ' + line);
156
+ }
157
+ }
158
+ }
159
+ }
160
+ return;
161
+ }
162
+
163
+ // Single computer mode (default or with --computer filter)
164
+ let targetReqPath = reqPath;
165
+
166
+ // If computer filter specified, find that computer's requirements file
167
+ if (options && options.computer) {
168
+ const vibeDir = path.join(repoPath, '.vibecodingmachine');
169
+ const files = await fs.readdir(vibeDir);
170
+ const computerFile = files.find(f =>
171
+ f.startsWith('REQUIREMENTS') &&
172
+ f.endsWith('.md') &&
173
+ f.toLowerCase().includes(options.computer.toLowerCase())
174
+ );
175
+
176
+ if (!computerFile) {
177
+ console.log(chalk.yellow(`No requirements file found for computer: ${options.computer}`));
178
+ return;
179
+ }
180
+
181
+ targetReqPath = path.join(vibeDir, computerFile);
182
+ }
183
+
184
+ if (!await fs.pathExists(targetReqPath)) {
106
185
  console.log(chalk.yellow('No REQUIREMENTS.md found.'));
107
186
  return;
108
187
  }
109
- const content = await fs.readFile(reqPath, 'utf8');
188
+
189
+ const content = await fs.readFile(targetReqPath, 'utf8');
110
190
  const lines = content.split('\n');
111
- const filter = options && options.status ? String(options.status).toLowerCase() : null;
191
+
192
+ // Extract focus area for focus filtering
193
+ let fileFocus = '';
194
+ const focusMatch = content.match(/## 🎯 Focus\n\*\*(.+)\*\*/);
195
+ if (focusMatch) {
196
+ fileFocus = focusMatch[1].trim();
197
+ }
198
+
199
+ // Check focus filter
200
+ if (options && options.focus) {
201
+ if (!fileFocus || !fileFocus.toLowerCase().includes(options.focus.toLowerCase())) {
202
+ console.log(chalk.yellow(`No requirements match focus area: ${options.focus}`));
203
+ return;
204
+ }
205
+ }
206
+
207
+ const statusFilter = options && options.status ? String(options.status).toLowerCase() : null;
112
208
  for (const line of lines) {
113
209
  if (line.startsWith('### ') || line.startsWith('## ')) {
114
- if (!filter || line.toLowerCase().includes(filter)) {
210
+ if (!statusFilter || line.toLowerCase().includes(statusFilter)) {
115
211
  console.log(line);
116
212
  }
117
213
  }
@@ -124,10 +220,14 @@ async function list(options) {
124
220
 
125
221
  async function add(name, pkg, description) {
126
222
  try {
127
- const { reqPath } = await getReqPathOrExit();
223
+ const { reqPath, repoPath } = await getReqPathOrExit();
128
224
  await fs.ensureFile(reqPath);
129
225
  let content = await fs.readFile(reqPath, 'utf8').catch(() => '');
130
226
 
227
+ // Get next requirement number
228
+ const { getNextRequirementNumber } = require('vibecodingmachine-core');
229
+ const reqNumber = await getNextRequirementNumber(repoPath);
230
+
131
231
  // Find the TODO section
132
232
  const todoSectionHeader = '## ⏳ Requirements not yet completed';
133
233
  if (!content.includes('Requirements not yet completed')) {
@@ -144,8 +244,8 @@ async function add(name, pkg, description) {
144
244
 
145
245
  // Insert right after the TODO section header
146
246
  if (!inserted && lines[i].startsWith('##') && lines[i].includes('Requirements not yet completed')) {
147
- // Add requirement header
148
- newLines.push(`### ${name}`);
247
+ // Add requirement header with number
248
+ newLines.push(`### R${reqNumber}: ${name}`);
149
249
 
150
250
  // Add package if provided (and not 'all')
151
251
  if (pkg && Array.isArray(pkg) && pkg.length > 0) {
@@ -363,6 +463,25 @@ async function working() {
363
463
  }
364
464
  }
365
465
 
466
+ async function numberAll() {
467
+ try {
468
+ const { reqPath, repoPath } = await getReqPathOrExit();
469
+ if (!await fs.pathExists(reqPath)) {
470
+ console.log(chalk.yellow('No REQUIREMENTS.md found.'));
471
+ return;
472
+ }
473
+
474
+ console.log(chalk.cyan('Numbering all existing requirements...'));
475
+ const { numberAllRequirements } = require('vibecodingmachine-core');
476
+ const numbered = await numberAllRequirements(reqPath, repoPath);
477
+
478
+ console.log(chalk.green(`✓ Numbered ${numbered} requirements`));
479
+ } catch (error) {
480
+ console.error(chalk.red('Error numbering requirements:'), error.message);
481
+ process.exit(1);
482
+ }
483
+ }
484
+
366
485
  module.exports = {
367
486
  list,
368
487
  add,
@@ -371,7 +490,8 @@ module.exports = {
371
490
  next,
372
491
  edit,
373
492
  watch,
374
- rename
493
+ rename,
494
+ numberAll
375
495
  };
376
496
 
377
497
 
@@ -4,6 +4,7 @@ const os = require('os');
4
4
  const chalk = require('chalk');
5
5
  const inquirer = require('inquirer');
6
6
  const { t } = require('vibecodingmachine-core');
7
+ const { promptWithDefaultsOnce } = require('../utils/prompt-helper');
7
8
 
8
9
  async function setupAlias() {
9
10
  console.log(chalk.bold.cyan(`\n🛠️ ${t('setup.alias.title')}\n`));
@@ -62,7 +63,7 @@ async function setupAlias() {
62
63
  }
63
64
 
64
65
  // Confirm with user
65
- const { confirm } = await inquirer.prompt([
66
+ const { confirm } = await promptWithDefaultsOnce([
66
67
  {
67
68
  type: 'confirm',
68
69
  name: 'confirm',
@@ -1,7 +1,7 @@
1
1
  const chalk = require('chalk');
2
2
  const Table = require('cli-table3');
3
3
  const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
4
- const { t } = require('vibecodingmachine-core');
4
+ const { t, errorReporter } = require('vibecodingmachine-core');
5
5
 
6
6
  /**
7
7
  * Trigger immediate sync
@@ -44,6 +44,9 @@ async function syncNow() {
44
44
  } catch (error) {
45
45
  console.error(chalk.red(`\n✗ ${t('sync.failed')}`), error.message);
46
46
  console.log(chalk.gray(`\n${t('sync.tip.aws')}\n`));
47
+ await errorReporter.reportError(error, {
48
+ command: 'syncNow'
49
+ });
47
50
  // Don't throw - just log the error
48
51
  } finally {
49
52
  syncEngine.stop();
@@ -109,6 +112,9 @@ async function syncStatus() {
109
112
 
110
113
  } catch (error) {
111
114
  console.error(chalk.red(`\n✗ ${t('sync.status.failed')}`), error.message);
115
+ await errorReporter.reportError(error, {
116
+ command: 'syncStatus'
117
+ });
112
118
  throw error;
113
119
  } finally {
114
120
  syncEngine.stop();
package/src/utils/auth.js CHANGED
@@ -8,7 +8,7 @@ const sharedAuth = require('vibecodingmachine-core/src/auth/shared-auth-storage'
8
8
  // AWS Cognito configuration
9
9
  const COGNITO_DOMAIN = process.env.COGNITO_DOMAIN || 'vibecodingmachine-auth-1763598779.auth.us-east-1.amazoncognito.com';
10
10
  const CLIENT_ID = process.env.COGNITO_APP_CLIENT_ID || '3tbe1i2g36uqule92iuk6snuo3'; // Public client (no secret)
11
- const PREFERRED_PORT = 3000; // Preferred port, will auto-detect if unavailable
11
+ const PREFERRED_PORT = 3001; // Use 3001 to avoid conflict with web dev server on 3000
12
12
 
13
13
  // Load shared access denied HTML
14
14
  function getAccessDeniedHTML() {
@@ -70,6 +70,17 @@ class CLIAuth {
70
70
  return await sharedAuth.getToken();
71
71
  }
72
72
 
73
+ /**
74
+ * Get auth token string for API requests
75
+ */
76
+ async getAuthToken() {
77
+ const token = await sharedAuth.getToken();
78
+ if (!token) return null;
79
+
80
+ // Handle both string token and object with id_token
81
+ return typeof token === 'string' ? token : token.id_token;
82
+ }
83
+
73
84
  /**
74
85
  * Get user profile (uses shared storage)
75
86
  */
@@ -332,12 +343,10 @@ class CLIAuth {
332
343
 
333
344
  if (!portAvailable) {
334
345
  console.log(chalk.red(`\n❌ Port ${PORT} is already in use!\n`));
335
- console.log(chalk.yellow('Port 3000 is required for authentication (OAuth callback).\n'));
336
- console.log(chalk.white('To fix this, find and stop the process using port 3000:\n'));
346
+ console.log(chalk.yellow(`Port ${PORT} is required for authentication (OAuth callback).\n`));
347
+ console.log(chalk.white(`To fix this, find and stop the process using port ${PORT}:\n`));
337
348
  console.log(chalk.gray(' 1. Find the process: ') + chalk.cyan(`lsof -i :${PORT}`));
338
- console.log(chalk.gray(' 2. Stop it: ') + chalk.cyan('kill <PID>'));
339
- console.log(chalk.gray('\nOr if you\'re running the web dev server:'));
340
- console.log(chalk.gray(' ') + chalk.cyan('cd packages/web && npm stop\n'));
349
+ console.log(chalk.gray(' 2. Stop it: ') + chalk.cyan('kill <PID>\n'));
341
350
  throw new Error(`Port ${PORT} is already in use. Please free up this port and try again.`);
342
351
  }
343
352
 
@@ -595,7 +604,7 @@ class CLIAuth {
595
604
  `client_id=${CLIENT_ID}&` +
596
605
  `response_type=code&` +
597
606
  `scope=email+openid+profile&` +
598
- `redirect_uri=http://localhost:3000/callback&` +
607
+ `redirect_uri=http://localhost:${PREFERRED_PORT}/callback&` +
599
608
  `code_challenge=${codeChallenge}&` +
600
609
  `code_challenge_method=S256&` +
601
610
  `identity_provider=Google&` +
@@ -610,7 +619,7 @@ class CLIAuth {
610
619
  console.log(chalk.yellow('3. Browser will redirect to localhost and show "connection refused" error'));
611
620
  console.log(chalk.yellow(' ' + chalk.gray('(This is expected - localhost is not accessible from your device)')));
612
621
  console.log(chalk.yellow('4. Copy the FULL URL from your browser address bar'));
613
- console.log(chalk.yellow(' ' + chalk.gray('(It will start with: http://localhost:3000/callback?code=...)')));
622
+ console.log(chalk.yellow(' ' + chalk.gray(`(It will start with: http://localhost:${PREFERRED_PORT}/callback?code=...)`)));
614
623
  console.log(chalk.yellow('5. Paste it below\n'));
615
624
 
616
625
  const { callbackUrl } = await inquirer.prompt([{
@@ -624,8 +633,8 @@ class CLIAuth {
624
633
  if (!input.includes('code=')) {
625
634
  return 'Invalid URL - should contain "code=" parameter';
626
635
  }
627
- if (!input.includes('localhost:3000/callback')) {
628
- return 'Invalid callback URL - should be from localhost:3000/callback';
636
+ if (!input.includes(`localhost:${PREFERRED_PORT}/callback`)) {
637
+ return `Invalid callback URL - should be from localhost:${PREFERRED_PORT}/callback`;
629
638
  }
630
639
  return true;
631
640
  }
@@ -640,14 +649,12 @@ class CLIAuth {
640
649
  throw new Error('No authorization code found in URL');
641
650
  }
642
651
 
643
- // Exchange code for tokens
644
- console.log(chalk.gray('Exchanging authorization code for tokens...'));
645
652
  // Exchange code for tokens
646
653
  console.log(chalk.gray('Exchanging authorization code for tokens...'));
647
654
  const tokens = await this._exchangeCodeForTokens(
648
655
  code,
649
656
  codeVerifier,
650
- 'http://localhost:3000/callback'
657
+ `http://localhost:${PREFERRED_PORT}/callback`
651
658
  );
652
659
 
653
660
  const idToken = tokens.id_token;
@@ -75,6 +75,17 @@ async function setStages(stages) {
75
75
  await writeConfig(cfg);
76
76
  }
77
77
 
78
+ async function getComputerFilter() {
79
+ const cfg = await readConfig();
80
+ return cfg.computerFilter || null;
81
+ }
82
+
83
+ async function setComputerFilter(computerName) {
84
+ const cfg = await readConfig();
85
+ cfg.computerFilter = computerName;
86
+ await writeConfig(cfg);
87
+ }
88
+
78
89
  module.exports = {
79
90
  getRepoPath,
80
91
  setRepoPath,
@@ -84,7 +95,9 @@ module.exports = {
84
95
  writeConfig,
85
96
  getStages,
86
97
  setStages,
87
- DEFAULT_STAGES
98
+ DEFAULT_STAGES,
99
+ getComputerFilter,
100
+ setComputerFilter
88
101
  };
89
102
 
90
103
 
@@ -7,6 +7,7 @@ const os = require('os');
7
7
  const { getProviderDefinitions, saveProviderPreferences, getDefaultProviderOrder } = require('./provider-registry');
8
8
  const { isKiroInstalled } = require('./kiro-installer');
9
9
  const { t } = require('vibecodingmachine-core');
10
+ const { promptWithDefaultsOnce } = require('./prompt-helper');
10
11
 
11
12
  const { execSync } = require('child_process');
12
13
 
@@ -120,7 +121,7 @@ async function checkFirstRun() {
120
121
  await new Promise(resolve => setTimeout(resolve, 1500));
121
122
 
122
123
  // --- NEW: Vibe Coding Introduction ---
123
- const { showIntro } = await inquirer.prompt([
124
+ const { showIntro } = await promptWithDefaultsOnce([
124
125
  {
125
126
  type: 'confirm',
126
127
  name: 'showIntro',
@@ -198,7 +199,7 @@ async function checkFirstRun() {
198
199
 
199
200
  // 3. Status Report & Selection
200
201
  if (detectedIDEs.length > 0) {
201
- console.log(chalk.green('\n✓ We found these IDEs and enabled them for you:'));
202
+ console.log(chalk.green('\n✓ We found these IDEs already installed and enabled them:'));
202
203
  detectedIDEs.forEach(ide => {
203
204
  console.log(chalk.gray(` • ${ide.name}`));
204
205
  });
@@ -206,19 +207,19 @@ async function checkFirstRun() {
206
207
 
207
208
  let selectedIDEs = [];
208
209
 
209
- // Only prompt for uninstalled IDEs if there are any
210
+ // Show all uninstalled IDEs for selection
210
211
  if (otherIDEs.length > 0) {
211
212
  console.log(); // Spacing
212
213
  const choices = otherIDEs.map(ide => ({
213
214
  name: ide.name,
214
215
  value: ide.id,
215
- checked: true // Select by default as requested
216
+ checked: true // All IDEs enabled by default as requested
216
217
  }));
217
218
 
218
219
  const response = await inquirer.prompt([{
219
220
  type: 'checkbox',
220
221
  name: 'selectedIDEs',
221
- message: 'Select additional IDEs to install & enable:',
222
+ message: 'Select IDEs to install & enable (uncheck any you don\'t want):',
222
223
  choices: choices,
223
224
  pageSize: 10
224
225
  }]);
@@ -331,6 +332,7 @@ async function checkFirstRun() {
331
332
  if (def && def.type === 'ide') {
332
333
  const isDetectedNow = reDetected.includes(id);
333
334
  const isSelected = selectedIDEs.includes(id);
335
+ // Enable if detected OR selected during first run
334
336
  enabledMap[id] = isDetectedNow || isSelected;
335
337
  } else {
336
338
  enabledMap[id] = true; // Keep LLMs enabled by default
@@ -345,7 +347,7 @@ async function checkFirstRun() {
345
347
 
346
348
  // --- NEW: CLI Usage Onboarding ---
347
349
  // Moved here so IDEs are set up first
348
- const { isFamiliar } = await inquirer.prompt([
350
+ const { isFamiliar } = await promptWithDefaultsOnce([
349
351
  {
350
352
  type: 'confirm',
351
353
  name: 'isFamiliar',