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.
- package/__tests__/antigravity-js-handler.test.js +23 -0
- package/__tests__/provider-manager.test.js +84 -0
- package/__tests__/provider-rate-cache.test.js +27 -0
- package/bin/vibecodingmachine.js +92 -118
- package/logs/audit/2025-12-27.jsonl +1 -0
- package/logs/audit/2026-01-03.jsonl +2 -0
- package/package.json +2 -2
- package/reset_provider_order.js +21 -0
- package/scripts/convert-requirements.js +35 -0
- package/scripts/debug-parse.js +24 -0
- package/src/commands/auth.js +5 -1
- package/src/commands/auto-direct.js +747 -182
- package/src/commands/auto.js +206 -48
- package/src/commands/computers.js +9 -0
- package/src/commands/feature.js +123 -0
- package/src/commands/ide.js +108 -3
- package/src/commands/repo.js +27 -22
- package/src/commands/requirements-remote.js +34 -2
- package/src/commands/requirements.js +129 -9
- package/src/commands/setup.js +2 -1
- package/src/commands/status.js +39 -1
- package/src/commands/sync.js +7 -1
- package/src/utils/antigravity-js-handler.js +13 -4
- package/src/utils/auth.js +56 -25
- package/src/utils/compliance-check.js +10 -0
- package/src/utils/config.js +42 -1
- package/src/utils/date-formatter.js +44 -0
- package/src/utils/first-run.js +8 -6
- package/src/utils/interactive.js +1363 -334
- package/src/utils/kiro-js-handler.js +188 -0
- package/src/utils/prompt-helper.js +64 -0
- package/src/utils/provider-rate-cache.js +31 -0
- package/src/utils/provider-registry.js +42 -1
- package/src/utils/requirements-converter.js +107 -0
- package/src/utils/requirements-parser.js +144 -0
- package/tests/antigravity-js-handler.test.js +23 -0
- package/tests/home-bootstrap.test.js +76 -0
- package/tests/integration/health-tracking.integration.test.js +284 -0
- package/tests/provider-manager.test.js +92 -0
- package/tests/rate-limit-display.test.js +44 -0
- package/tests/requirements-bullet-parsing.test.js +15 -0
- package/tests/requirements-converter.test.js +42 -0
- package/tests/requirements-heading-count.test.js +27 -0
- package/tests/requirements-legacy-parsing.test.js +15 -0
- package/tests/requirements-parse-integration.test.js +44 -0
- package/tests/wait-for-ide-completion.test.js +56 -0
- package/tests/wait-for-ide-quota-detection-cursor-screenshot.test.js +61 -0
- package/tests/wait-for-ide-quota-detection-cursor.test.js +60 -0
- package/tests/wait-for-ide-quota-detection-negative.test.js +45 -0
- package/tests/wait-for-ide-quota-detection.test.js +59 -0
- package/verify_fix.js +36 -0
- package/verify_ui.js +38 -0
package/src/commands/repo.js
CHANGED
|
@@ -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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
188
|
+
|
|
189
|
+
const content = await fs.readFile(targetReqPath, 'utf8');
|
|
110
190
|
const lines = content.split('\n');
|
|
111
|
-
|
|
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 (!
|
|
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
|
|
package/src/commands/setup.js
CHANGED
|
@@ -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
|
|
66
|
+
const { confirm } = await promptWithDefaultsOnce([
|
|
66
67
|
{
|
|
67
68
|
type: 'confirm',
|
|
68
69
|
name: 'confirm',
|
package/src/commands/status.js
CHANGED
|
@@ -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
|
|
package/src/commands/sync.js
CHANGED
|
@@ -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.
|
|
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:
|
|
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 =
|
|
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(
|
|
336
|
-
console.log(chalk.white(
|
|
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
|
|
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(
|
|
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(
|
|
628
|
-
return
|
|
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
|
-
|
|
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) {
|