vibecodingmachine-cli 2025.12.25-25 → 2026.1.22-1441

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.
Files changed (52) hide show
  1. package/__tests__/antigravity-js-handler.test.js +23 -0
  2. package/__tests__/provider-manager.test.js +84 -0
  3. package/__tests__/provider-rate-cache.test.js +27 -0
  4. package/bin/vibecodingmachine.js +92 -118
  5. package/logs/audit/2025-12-27.jsonl +1 -0
  6. package/logs/audit/2026-01-03.jsonl +2 -0
  7. package/package.json +2 -2
  8. package/reset_provider_order.js +21 -0
  9. package/scripts/convert-requirements.js +35 -0
  10. package/scripts/debug-parse.js +24 -0
  11. package/src/commands/auth.js +5 -1
  12. package/src/commands/auto-direct.js +747 -182
  13. package/src/commands/auto.js +206 -48
  14. package/src/commands/computers.js +9 -0
  15. package/src/commands/feature.js +123 -0
  16. package/src/commands/ide.js +108 -3
  17. package/src/commands/repo.js +27 -22
  18. package/src/commands/requirements-remote.js +34 -2
  19. package/src/commands/requirements.js +129 -9
  20. package/src/commands/setup.js +2 -1
  21. package/src/commands/status.js +39 -1
  22. package/src/commands/sync.js +7 -1
  23. package/src/utils/antigravity-js-handler.js +13 -4
  24. package/src/utils/auth.js +56 -25
  25. package/src/utils/compliance-check.js +10 -0
  26. package/src/utils/config.js +42 -1
  27. package/src/utils/date-formatter.js +44 -0
  28. package/src/utils/first-run.js +8 -6
  29. package/src/utils/interactive.js +1363 -334
  30. package/src/utils/kiro-js-handler.js +188 -0
  31. package/src/utils/prompt-helper.js +64 -0
  32. package/src/utils/provider-rate-cache.js +31 -0
  33. package/src/utils/provider-registry.js +42 -1
  34. package/src/utils/requirements-converter.js +107 -0
  35. package/src/utils/requirements-parser.js +144 -0
  36. package/tests/antigravity-js-handler.test.js +23 -0
  37. package/tests/home-bootstrap.test.js +76 -0
  38. package/tests/integration/health-tracking.integration.test.js +284 -0
  39. package/tests/provider-manager.test.js +92 -0
  40. package/tests/rate-limit-display.test.js +44 -0
  41. package/tests/requirements-bullet-parsing.test.js +15 -0
  42. package/tests/requirements-converter.test.js +42 -0
  43. package/tests/requirements-heading-count.test.js +27 -0
  44. package/tests/requirements-legacy-parsing.test.js +15 -0
  45. package/tests/requirements-parse-integration.test.js +44 -0
  46. package/tests/wait-for-ide-completion.test.js +56 -0
  47. package/tests/wait-for-ide-quota-detection-cursor-screenshot.test.js +61 -0
  48. package/tests/wait-for-ide-quota-detection-cursor.test.js +60 -0
  49. package/tests/wait-for-ide-quota-detection-negative.test.js +45 -0
  50. package/tests/wait-for-ide-quota-detection.test.js +59 -0
  51. package/verify_fix.js +36 -0
  52. package/verify_ui.js +38 -0
@@ -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
  }
@@ -282,11 +305,20 @@ function parseRequirements(content) {
282
305
 
283
306
  // Parse requirements
284
307
  if (currentSection && line.startsWith('### ')) {
308
+ const title = line.replace(/^###\s*/, '').trim();
309
+
310
+ // Skip header-only lines like "Conflict Resolution:" which are used as group labels
311
+ if (/^Conflict Resolution:?$/i.test(title)) {
312
+ // Reset currentRequirement so we don't treat this header as an actual requirement
313
+ currentRequirement = null;
314
+ continue;
315
+ }
316
+
285
317
  if (currentRequirement) {
286
318
  sections[currentSection].push(currentRequirement);
287
319
  }
288
320
  currentRequirement = {
289
- title: line.replace(/^###\s*/, '').trim(),
321
+ title,
290
322
  description: ''
291
323
  };
292
324
  } else if (currentRequirement && line.trim() && !line.startsWith('#')) {
@@ -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',
@@ -4,7 +4,7 @@ const os = require('os');
4
4
  const chalk = require('chalk');
5
5
  const { getRepoPath } = require('../utils/config');
6
6
  const { checkAutoModeStatus } = require('../utils/auto-mode');
7
- const { getRequirementsPath, t } = require('vibecodingmachine-core');
7
+ const { getRequirementsPath, t, IDEHealthTracker } = require('vibecodingmachine-core');
8
8
 
9
9
  /**
10
10
  * Format IDE name for display
@@ -34,12 +34,50 @@ async function show() {
34
34
  const repoPath = await getRepoPath();
35
35
  console.log(chalk.bold(`\n${t('status.title')}`));
36
36
  console.log(chalk.gray(`${t('status.repository')}:`), chalk.cyan(formatPath(repoPath)));
37
+
37
38
  const status = await checkAutoModeStatus();
38
39
  console.log(chalk.gray(`${t('status.auto.mode')}:`), status.running ? chalk.green(t('status.running')) : chalk.yellow(t('status.stopped')));
40
+
39
41
  if (status.running) {
40
42
  console.log(chalk.gray(`${t('status.ide')}:`), chalk.cyan(formatIDEName(status.ide || 'cline')));
41
43
  console.log(chalk.gray(`${t('status.chats')}:`), chalk.cyan(status.chatCount || 0));
42
44
  }
45
+
46
+ // Display IDE health metrics
47
+ try {
48
+ const healthTracker = new IDEHealthTracker();
49
+ const allMetrics = await healthTracker.getAllHealthMetrics();
50
+
51
+ if (allMetrics.size > 0) {
52
+ console.log(chalk.bold('\n📊 IDE Health Metrics:'));
53
+
54
+ for (const [ideId, metrics] of allMetrics) {
55
+ const successRate = metrics.successRate > 0 ? `${(metrics.successRate * 100).toFixed(1)}%` : 'N/A';
56
+ const avgResponseTime = metrics.averageResponseTime > 0 ? `${Math.round(metrics.averageResponseTime / 1000)}s` : 'N/A';
57
+
58
+ console.log(chalk.gray(` ${formatIDEName(ideId)}:`));
59
+ console.log(chalk.gray(` Success Rate:`), chalk.green(successRate));
60
+ console.log(chalk.gray(` Avg Response:`), chalk.cyan(avgResponseTime));
61
+ console.log(chalk.gray(` Interactions:`), chalk.blue(`${metrics.totalInteractions}`));
62
+ console.log(chalk.gray(` Health:`),
63
+ metrics.consecutiveFailures === 0 ?
64
+ chalk.green('✅ Healthy') :
65
+ chalk.yellow(`⚠️ ${metrics.consecutiveFailures} consecutive failures`)
66
+ );
67
+ console.log();
68
+ }
69
+
70
+ // Show recommended IDE
71
+ const recommendedIDE = await healthTracker.getRecommendedIDE();
72
+ if (recommendedIDE) {
73
+ console.log(chalk.bold('💡 Recommended IDE:'), chalk.cyan(formatIDEName(recommendedIDE)));
74
+ }
75
+ }
76
+ } catch (error) {
77
+ // Health tracking not available or error reading metrics
78
+ console.log(chalk.gray('\n📊 IDE Health:'), chalk.yellow('Not available'));
79
+ }
80
+
43
81
  console.log();
44
82
  }
45
83
 
@@ -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();
@@ -3,6 +3,7 @@ const {
3
3
  getProviderPreferences,
4
4
  saveProviderPreferences
5
5
  } = require('./provider-registry');
6
+ const { AppleScriptManager } = require('vibecodingmachine-core');
6
7
 
7
8
  /**
8
9
  * Check if Antigravity agent has hit a rate limit.
@@ -34,12 +35,20 @@ function checkAntigravityRateLimit(stderr) {
34
35
  * @returns {Promise<{success: boolean, nextProvider: string|null, error: string|null}>}
35
36
  */
36
37
  async function handleAntigravityRateLimit() {
37
- console.log(chalk.yellow('Antigravity rate limit detected. Disabling for this session.'));
38
+ console.log(chalk.yellow('Antigravity rate limit detected. Attempting to switch models within Antigravity first...'));
38
39
 
39
40
  try {
41
+ const appleScript = new AppleScriptManager();
42
+ const result = await appleScript.handleAntigravityQuotaLimit();
43
+
44
+ if (result.success) {
45
+ console.log(chalk.green(`Successfully switched to Antigravity model: ${result.model}`));
46
+ return { success: true, nextProvider: 'antigravity', modelSwitched: true, nextModel: result.model, error: null };
47
+ }
48
+
49
+ console.log(chalk.yellow(`Could not switch models within Antigravity: ${result.error || 'Unknown error'}. Switching to next provider...`));
50
+
40
51
  const prefs = await getProviderPreferences();
41
- prefs.enabled.antigravity = false;
42
- await saveProviderPreferences(prefs);
43
52
 
44
53
  const nextProvider = prefs.order.find(p => p !== 'antigravity' && prefs.enabled[p]);
45
54
 
@@ -50,7 +59,7 @@ async function handleAntigravityRateLimit() {
50
59
  return { success: false, nextProvider: null, error: 'No fallback providers available.' };
51
60
  }
52
61
  } catch (error) {
53
- return { success: false, nextProvider: null, error: 'Failed to update provider preferences.' };
62
+ return { success: false, nextProvider: null, error: `Failed to handle rate limit: ${error.message}` };
54
63
  }
55
64
  }
56
65
 
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() {
@@ -50,7 +50,7 @@ class CLIAuth {
50
50
 
51
51
  // Save new tokens
52
52
  await sharedAuth.saveToken(newTokens);
53
-
53
+
54
54
  // Update user activity in database
55
55
  await this._updateUserActivity();
56
56
  return true;
@@ -70,6 +70,22 @@ class CLIAuth {
70
70
  return await sharedAuth.getToken();
71
71
  }
72
72
 
73
+ /**
74
+ * Get auth token string for API requests
75
+ * Ensures the token is valid and refreshed if necessary
76
+ */
77
+ async getAuthToken() {
78
+ // Check if authenticated and refresh if needed
79
+ const isAuth = await this.isAuthenticated();
80
+ if (!isAuth) return null;
81
+
82
+ const token = await sharedAuth.getToken();
83
+ if (!token) return null;
84
+
85
+ // Handle both string token and object with id_token
86
+ return typeof token === 'string' ? token : token.id_token;
87
+ }
88
+
73
89
  /**
74
90
  * Get user profile (uses shared storage)
75
91
  */
@@ -332,12 +348,10 @@ class CLIAuth {
332
348
 
333
349
  if (!portAvailable) {
334
350
  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'));
351
+ console.log(chalk.yellow(`Port ${PORT} is required for authentication (OAuth callback).\n`));
352
+ console.log(chalk.white(`To fix this, find and stop the process using port ${PORT}:\n`));
337
353
  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'));
354
+ console.log(chalk.gray(' 2. Stop it: ') + chalk.cyan('kill <PID>\n'));
341
355
  throw new Error(`Port ${PORT} is already in use. Please free up this port and try again.`);
342
356
  }
343
357
 
@@ -595,7 +609,7 @@ class CLIAuth {
595
609
  `client_id=${CLIENT_ID}&` +
596
610
  `response_type=code&` +
597
611
  `scope=email+openid+profile&` +
598
- `redirect_uri=http://localhost:3000/callback&` +
612
+ `redirect_uri=http://localhost:${PREFERRED_PORT}/callback&` +
599
613
  `code_challenge=${codeChallenge}&` +
600
614
  `code_challenge_method=S256&` +
601
615
  `identity_provider=Google&` +
@@ -610,7 +624,7 @@ class CLIAuth {
610
624
  console.log(chalk.yellow('3. Browser will redirect to localhost and show "connection refused" error'));
611
625
  console.log(chalk.yellow(' ' + chalk.gray('(This is expected - localhost is not accessible from your device)')));
612
626
  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=...)')));
627
+ console.log(chalk.yellow(' ' + chalk.gray(`(It will start with: http://localhost:${PREFERRED_PORT}/callback?code=...)`)));
614
628
  console.log(chalk.yellow('5. Paste it below\n'));
615
629
 
616
630
  const { callbackUrl } = await inquirer.prompt([{
@@ -624,8 +638,8 @@ class CLIAuth {
624
638
  if (!input.includes('code=')) {
625
639
  return 'Invalid URL - should contain "code=" parameter';
626
640
  }
627
- if (!input.includes('localhost:3000/callback')) {
628
- return 'Invalid callback URL - should be from localhost:3000/callback';
641
+ if (!input.includes(`localhost:${PREFERRED_PORT}/callback`)) {
642
+ return `Invalid callback URL - should be from localhost:${PREFERRED_PORT}/callback`;
629
643
  }
630
644
  return true;
631
645
  }
@@ -640,14 +654,12 @@ class CLIAuth {
640
654
  throw new Error('No authorization code found in URL');
641
655
  }
642
656
 
643
- // Exchange code for tokens
644
- console.log(chalk.gray('Exchanging authorization code for tokens...'));
645
657
  // Exchange code for tokens
646
658
  console.log(chalk.gray('Exchanging authorization code for tokens...'));
647
659
  const tokens = await this._exchangeCodeForTokens(
648
660
  code,
649
661
  codeVerifier,
650
- 'http://localhost:3000/callback'
662
+ `http://localhost:${PREFERRED_PORT}/callback`
651
663
  );
652
664
 
653
665
  const idToken = tokens.id_token;
@@ -711,10 +723,10 @@ class CLIAuth {
711
723
  try {
712
724
  // Decode JWT to get user info (without verification since we already validated)
713
725
  const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
714
-
726
+
715
727
  const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
716
728
  const userDb = new UserDatabase();
717
-
729
+
718
730
  const userInfo = {
719
731
  email: payload.email,
720
732
  name: payload.name || payload.email.split('@')[0],
@@ -723,7 +735,7 @@ class CLIAuth {
723
735
 
724
736
  // Register/update user
725
737
  const user = await userDb.registerUser(userInfo);
726
-
738
+
727
739
  // Register computer
728
740
  await userDb.registerComputer(user.userId, {
729
741
  interface: 'cli'
@@ -756,12 +768,12 @@ class CLIAuth {
756
768
 
757
769
  // Decode JWT to get user info
758
770
  const payload = JSON.parse(Buffer.from(token.id_token.split('.')[1], 'base64').toString());
759
-
771
+
760
772
  const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
761
773
  const userDb = new UserDatabase();
762
-
774
+
763
775
  const userId = userDb.generateUserId(payload.email);
764
-
776
+
765
777
  // Update last activity
766
778
  await userDb.updateUserActivity(userId, {
767
779
  lastActivity: Date.now()
@@ -786,12 +798,12 @@ class CLIAuth {
786
798
  if (!idToken) return;
787
799
 
788
800
  const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
789
-
801
+
790
802
  const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
791
803
  const userDb = new UserDatabase();
792
-
804
+
793
805
  const userId = userDb.generateUserId(payload.email);
794
-
806
+
795
807
  await userDb.trackActivity(userId, {
796
808
  interface: 'cli',
797
809
  action,
@@ -821,12 +833,31 @@ class CLIAuth {
821
833
  if (!idToken) return null;
822
834
 
823
835
  const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
824
-
836
+
825
837
  const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
826
838
  const userDb = new UserDatabase();
839
+
840
+ // Get user from API to get the correct userId format
841
+ const userDbClient = userDb.apiClient;
842
+ userDbClient.setAuthToken(idToken);
827
843
 
844
+ try {
845
+ const apiUser = await userDbClient.getUser();
846
+ if (apiUser && apiUser.userId) {
847
+ return {
848
+ userId: apiUser.userId, // Use API server's userId format
849
+ email: payload.email,
850
+ name: payload.name || payload.email.split('@')[0],
851
+ cognitoId: payload.sub
852
+ };
853
+ }
854
+ } catch (error) {
855
+ console.warn('Could not fetch user from API:', error.message);
856
+ }
857
+
858
+ // Fallback to generated userId if API call fails
828
859
  const userId = userDb.generateUserId(payload.email);
829
-
860
+
830
861
  return {
831
862
  userId,
832
863
  email: payload.email,
@@ -38,8 +38,18 @@ async function checkCompliance() {
38
38
  return true
39
39
  }
40
40
 
41
+ // Set auth token for database operations
42
+ const token = await auth.getAuthToken()
43
+ if (token) {
44
+ const UserDatabase = require('vibecodingmachine-core/src/database/user-schema')
45
+ const userDb = new UserDatabase()
46
+ userDb.setAuthToken(token)
47
+ }
48
+
41
49
  // Check and prompt for compliance
42
50
  const compliancePrompt = new CompliancePrompt()
51
+ // Set auth token for compliance manager
52
+ compliancePrompt.complianceManager.userDb.setAuthToken(token)
43
53
  const status = await compliancePrompt.complianceManager.checkComplianceStatus(user.userId)
44
54
 
45
55
  if (!status.needsAcknowledgment) {