vibecodingmachine-cli 2025.12.1-534 ā 2025.12.22-2230
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/bin/vibecodingmachine.js +301 -12
- package/package.json +5 -2
- package/repro_open.js +13 -0
- package/reproduce_issue.js +160 -0
- package/scripts/postinstall.js +80 -0
- package/src/commands/auth.js +0 -1
- package/src/commands/auto-direct.js +455 -136
- package/src/commands/auto.js +488 -163
- package/src/commands/computers.js +306 -0
- package/src/commands/repo.js +0 -1
- package/src/commands/requirements-remote.js +308 -0
- package/src/commands/requirements.js +233 -16
- package/src/commands/status.js +0 -1
- package/src/commands/sync.js +280 -0
- package/src/utils/agent-selector.js +50 -0
- package/src/utils/antigravity-installer.js +212 -0
- package/src/utils/antigravity-js-handler.js +60 -0
- package/src/utils/asset-cleanup.js +60 -0
- package/src/utils/auth.js +232 -8
- package/src/utils/auto-mode-ansi-ui.js +0 -1
- package/src/utils/auto-mode-simple-ui.js +3 -23
- package/src/utils/compliance-check.js +166 -0
- package/src/utils/config.js +27 -1
- package/src/utils/copy-with-progress.js +167 -0
- package/src/utils/download-with-progress.js +84 -0
- package/src/utils/first-run.js +410 -0
- package/src/utils/interactive.js +1197 -391
- package/src/utils/kiro-installer.js +178 -0
- package/src/utils/persistent-header.js +1 -3
- package/src/utils/provider-registry.js +13 -4
- package/src/utils/status-card.js +2 -1
- package/src/utils/user-tracking.js +300 -0
- package/tests/requirements-navigator-buildtree-await.test.js +28 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const ora = require('ora');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
async function installAntigravity() {
|
|
9
|
+
console.log(chalk.cyan('\nš Initiating Google Antigravity IDE Installation...'));
|
|
10
|
+
|
|
11
|
+
const spinner = ora('Checking system requirements...').start();
|
|
12
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
13
|
+
|
|
14
|
+
if (os.platform() !== 'darwin') {
|
|
15
|
+
spinner.fail('Automated Antigravity installation is only supported on macOS');
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const arch = os.arch();
|
|
20
|
+
const isArm = arch === 'arm64';
|
|
21
|
+
|
|
22
|
+
// URLs from electron-app IDE installer
|
|
23
|
+
const downloadUrl = isArm
|
|
24
|
+
? 'https://edgedl.me.gvt1.com/edgedl/release2/j0qc3/antigravity/stable/1.11.2-6251250307170304/darwin-arm/Antigravity.dmg'
|
|
25
|
+
: 'https://edgedl.me.gvt1.com/edgedl/release2/j0qc3/antigravity/stable/1.11.2-6251250307170304/darwin-x64/Antigravity.dmg';
|
|
26
|
+
|
|
27
|
+
spinner.succeed(`System OK (${isArm ? 'Apple Silicon' : 'Intel'})`);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const cacheDir = path.join(os.homedir(), '.vibecodingmachine', 'cache');
|
|
31
|
+
await fs.ensureDir(cacheDir);
|
|
32
|
+
const cachedPath = path.join(cacheDir, 'Antigravity.dmg');
|
|
33
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'antigravity-'));
|
|
34
|
+
const dmgPath = path.join(tempDir, 'Antigravity.dmg');
|
|
35
|
+
const { downloadWithProgress } = require('./download-with-progress');
|
|
36
|
+
|
|
37
|
+
// Prefer cached DMG if it exists and matches upstream size (or exists at all)
|
|
38
|
+
let usedCache = false;
|
|
39
|
+
try {
|
|
40
|
+
if (await fs.pathExists(cachedPath)) {
|
|
41
|
+
// Try HEAD to compare sizes
|
|
42
|
+
try {
|
|
43
|
+
const fetch = require('node-fetch');
|
|
44
|
+
const head = await fetch(downloadUrl, { method: 'HEAD' });
|
|
45
|
+
const total = Number(head.headers.get('content-length')) || 0;
|
|
46
|
+
const stat = await fs.stat(cachedPath);
|
|
47
|
+
if (total && stat.size === total) {
|
|
48
|
+
await fs.copy(cachedPath, dmgPath);
|
|
49
|
+
usedCache = true;
|
|
50
|
+
console.log(chalk.gray('Using cached Antigravity DMG'));
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// If HEAD fails, still allow reuse of cache to avoid re-download
|
|
54
|
+
await fs.copy(cachedPath, dmgPath);
|
|
55
|
+
usedCache = true;
|
|
56
|
+
console.log(chalk.gray('Using cached Antigravity DMG (no HEAD)'));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (e) {
|
|
60
|
+
// ignore cache errors
|
|
61
|
+
usedCache = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Use download helper which displays progress and ETA if we didn't reuse cache
|
|
65
|
+
if (!usedCache) {
|
|
66
|
+
await downloadWithProgress(downloadUrl, dmgPath, { label: 'Downloading Antigravity...' });
|
|
67
|
+
// Save to cache for future runs
|
|
68
|
+
try { await fs.copy(dmgPath, cachedPath); } catch (e) { /* ignore cache write errors */ }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const installSpinner = ora('Installing Antigravity...').start();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
installSpinner.text = 'Mounting disk image...';
|
|
75
|
+
execSync(`hdiutil attach "${dmgPath}" -nobrowse -noverify -mountpoint "${tempDir}/mount"`);
|
|
76
|
+
|
|
77
|
+
installSpinner.text = 'Copying to /Applications...';
|
|
78
|
+
const files = await fs.readdir(path.join(tempDir, 'mount'));
|
|
79
|
+
const appName = files.find((f) => f.endsWith('.app'));
|
|
80
|
+
if (!appName) throw new Error('Could not find .app in DMG');
|
|
81
|
+
|
|
82
|
+
const src = path.join(tempDir, 'mount', appName);
|
|
83
|
+
const dest = path.join('/Applications', appName);
|
|
84
|
+
|
|
85
|
+
if (await fs.pathExists(dest)) {
|
|
86
|
+
installSpinner.text = 'Replacing existing application...';
|
|
87
|
+
await fs.remove(dest);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Prefer rsync/ditto; fallback to node-stream copy with progress
|
|
91
|
+
try {
|
|
92
|
+
const { copyAppWithProgress } = require('./copy-with-progress');
|
|
93
|
+
installSpinner.stop();
|
|
94
|
+
const ok = await copyAppWithProgress(src, dest, { spinner: installSpinner });
|
|
95
|
+
if (!ok) {
|
|
96
|
+
installSpinner.text = 'Copying (fs fallback) to /Applications...';
|
|
97
|
+
await fs.copy(src, dest);
|
|
98
|
+
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
// final fallback
|
|
101
|
+
installSpinner.text = 'Copying (fs fallback) to /Applications...';
|
|
102
|
+
try {
|
|
103
|
+
execSync(`cp -R "${src}" "/Applications/"`, { stdio: 'inherit' });
|
|
104
|
+
} catch (err) {
|
|
105
|
+
await fs.copy(src, dest);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
installSpinner.text = 'Cleaning up...';
|
|
110
|
+
execSync(`hdiutil detach "${tempDir}/mount" -force`);
|
|
111
|
+
await fs.remove(tempDir);
|
|
112
|
+
|
|
113
|
+
installSpinner.succeed('Antigravity installed');
|
|
114
|
+
console.log(chalk.green(`\nā
Installed to ${dest}`));
|
|
115
|
+
// Attempt to automatically configure Antigravity's first-run settings
|
|
116
|
+
try {
|
|
117
|
+
await configureAntigravityDefaults(dest);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
// Non-fatal: log and continue
|
|
120
|
+
console.log(chalk.yellow('Warning: could not auto-configure Antigravity:'), e.message || e);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return true;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
installSpinner.fail('Installation failed');
|
|
126
|
+
try {
|
|
127
|
+
if (await fs.pathExists(path.join(tempDir, 'mount'))) {
|
|
128
|
+
execSync(`hdiutil detach "${tempDir}/mount" -force`);
|
|
129
|
+
}
|
|
130
|
+
try { await fs.remove(tempDir); } catch (e) { /* ignore */ }
|
|
131
|
+
console.log(chalk.red('\nInstallation error:'), err.message);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.log(chalk.red('\nFailed to install Antigravity:'), error.message);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function configureAntigravityDefaults() {
|
|
141
|
+
// Attempt to open Antigravity and set the onboarding defaults via AppleScript
|
|
142
|
+
// Desired selections:
|
|
143
|
+
// - Agent-driven development (instead of recommended Agent-assisted)
|
|
144
|
+
// - Terminal execution policy: Turbo
|
|
145
|
+
// - Review policy: Always Proceed
|
|
146
|
+
// - Use the default allowlist for the browser (checked)
|
|
147
|
+
try {
|
|
148
|
+
const script = `
|
|
149
|
+
tell application "Antigravity"
|
|
150
|
+
activate
|
|
151
|
+
end tell
|
|
152
|
+
delay 0.8
|
|
153
|
+
tell application "System Events"
|
|
154
|
+
tell process "Antigravity"
|
|
155
|
+
set frontmost to true
|
|
156
|
+
delay 0.5
|
|
157
|
+
-- Try to select Agent-driven development radio button
|
|
158
|
+
try
|
|
159
|
+
click radio button "Agent-driven development" of radio group 1 of window 1
|
|
160
|
+
delay 0.2
|
|
161
|
+
end try
|
|
162
|
+
|
|
163
|
+
-- Set Terminal execution policy to Turbo (attempt by label, then by index)
|
|
164
|
+
try
|
|
165
|
+
try
|
|
166
|
+
click pop up button "Terminal execution policy" of window 1
|
|
167
|
+
delay 0.2
|
|
168
|
+
click menu item "Turbo" of menu 1 of pop up button "Terminal execution policy" of window 1
|
|
169
|
+
on error
|
|
170
|
+
-- fallback: click first pop up button and choose Turbo
|
|
171
|
+
click pop up button 1 of window 1
|
|
172
|
+
delay 0.2
|
|
173
|
+
click menu item "Turbo" of menu 1 of pop up button 1 of window 1
|
|
174
|
+
end try
|
|
175
|
+
delay 0.2
|
|
176
|
+
end try
|
|
177
|
+
|
|
178
|
+
-- Set Review policy to Always Proceed
|
|
179
|
+
try
|
|
180
|
+
click pop up button "Review policy" of window 1
|
|
181
|
+
delay 0.2
|
|
182
|
+
click menu item "Always Proceed" of menu 1 of pop up button "Review policy" of window 1
|
|
183
|
+
delay 0.2
|
|
184
|
+
end try
|
|
185
|
+
|
|
186
|
+
-- Ensure default allowlist checkbox is checked
|
|
187
|
+
try
|
|
188
|
+
set cb to checkbox "Use the default allowlist for the browser" of window 1
|
|
189
|
+
if (value of cb as boolean) is false then click cb
|
|
190
|
+
delay 0.2
|
|
191
|
+
end try
|
|
192
|
+
|
|
193
|
+
-- Advance the onboarding if a Next/Done button exists
|
|
194
|
+
try
|
|
195
|
+
if exists button "Next" of window 1 then click button "Next" of window 1
|
|
196
|
+
delay 0.3
|
|
197
|
+
end try
|
|
198
|
+
end tell
|
|
199
|
+
end tell
|
|
200
|
+
`;
|
|
201
|
+
|
|
202
|
+
// Use osascript to run the UI automation. JSON-stringify ensures safe quoting.
|
|
203
|
+
const { execSync } = require('child_process');
|
|
204
|
+
execSync(`osascript -e ${JSON.stringify(script)}`, { stdio: 'ignore', timeout: 15000 });
|
|
205
|
+
console.log(chalk.gray('Auto-configured Antigravity first-run preferences'));
|
|
206
|
+
return true;
|
|
207
|
+
} catch (err) {
|
|
208
|
+
throw new Error(err.message || err);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = { installAntigravity };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const {
|
|
3
|
+
getProviderPreferences,
|
|
4
|
+
saveProviderPreferences
|
|
5
|
+
} = require('./provider-registry');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check if Antigravity agent has hit a rate limit.
|
|
9
|
+
* @param {string} stderr - Standard error output from the agent.
|
|
10
|
+
* @returns {{isRateLimited: boolean, message: string|null}} - Rate limit status and message.
|
|
11
|
+
*/
|
|
12
|
+
function checkAntigravityRateLimit(stderr) {
|
|
13
|
+
const rateLimitPatterns = [
|
|
14
|
+
/quota limit/i,
|
|
15
|
+
/rate limit/i,
|
|
16
|
+
/too many requests/i,
|
|
17
|
+
/limit exceeded/i
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
for (const pattern of rateLimitPatterns) {
|
|
21
|
+
if (pattern.test(stderr)) {
|
|
22
|
+
return {
|
|
23
|
+
isRateLimited: true,
|
|
24
|
+
message: 'Antigravity quota limit reached.'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { isRateLimited: false, message: null };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Handle rate limit for Antigravity by disabling it and selecting the next available provider.
|
|
34
|
+
* @returns {Promise<{success: boolean, nextProvider: string|null, error: string|null}>}
|
|
35
|
+
*/
|
|
36
|
+
async function handleAntigravityRateLimit() {
|
|
37
|
+
console.log(chalk.yellow('Antigravity rate limit detected. Disabling for this session.'));
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const prefs = await getProviderPreferences();
|
|
41
|
+
prefs.enabled.antigravity = false;
|
|
42
|
+
await saveProviderPreferences(prefs);
|
|
43
|
+
|
|
44
|
+
const nextProvider = prefs.order.find(p => p !== 'antigravity' && prefs.enabled[p]);
|
|
45
|
+
|
|
46
|
+
if (nextProvider) {
|
|
47
|
+
console.log(chalk.cyan(`Switching to next available provider: ${nextProvider}`));
|
|
48
|
+
return { success: true, nextProvider, error: null };
|
|
49
|
+
} else {
|
|
50
|
+
return { success: false, nextProvider: null, error: 'No fallback providers available.' };
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return { success: false, nextProvider: null, error: 'Failed to update provider preferences.' };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
checkAntigravityRateLimit,
|
|
59
|
+
handleAntigravityRateLimit
|
|
60
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Scans the assets directory and removes 0-byte PNG files
|
|
6
|
+
* This is a mitigation for an issue where empty PNG files are mysteriously created
|
|
7
|
+
*/
|
|
8
|
+
async function cleanupBrokenAssets() {
|
|
9
|
+
try {
|
|
10
|
+
// Find assets directory - assuming we are in packages/cli/src/utils
|
|
11
|
+
// and assets is at root of repo, but simpler to look relative to where VCM runs
|
|
12
|
+
// effectively, we look for 'assets' in the current working directory as CWD is usually the repo root or where the CLI is run
|
|
13
|
+
// Better: define path relative to the project root if possible, or CWD if that's how it's structured.
|
|
14
|
+
// Based on previous search, assets/ is at the root of the repo.
|
|
15
|
+
// However, when installed, strict paths matters.
|
|
16
|
+
// But for the user's workspace context, iterating 'assets/' in CWD (repo root) is correct for their dev environment.
|
|
17
|
+
// Let's try to be smart about finding specific 'assets' dir if we can, but CWD is a safe start for the CLI tool which is often run from root.
|
|
18
|
+
|
|
19
|
+
// Actually, looking at previous grep, `assets/` was checked specifically.
|
|
20
|
+
// Let's just check `assets` in process.cwd() as a primary target.
|
|
21
|
+
|
|
22
|
+
const assetsDir = path.join(process.cwd(), 'assets');
|
|
23
|
+
|
|
24
|
+
if (!await fs.pathExists(assetsDir)) {
|
|
25
|
+
return; // No assets dir, nothing to clean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const files = await fs.readdir(assetsDir);
|
|
29
|
+
let removedCount = 0;
|
|
30
|
+
|
|
31
|
+
for (const file of files) {
|
|
32
|
+
if (file.toLowerCase().endsWith('.png')) {
|
|
33
|
+
const filePath = path.join(assetsDir, file);
|
|
34
|
+
try {
|
|
35
|
+
const stats = await fs.stat(filePath);
|
|
36
|
+
if (stats.size === 0) {
|
|
37
|
+
await fs.remove(filePath);
|
|
38
|
+
removedCount++;
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// Ignore errors accessing specific files
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (removedCount > 0) {
|
|
47
|
+
// Only log if we actually did something, to keep noise down
|
|
48
|
+
// But maybe we want to know? The plan said "verification: verify removed".
|
|
49
|
+
// We can use a debug flag or just log.
|
|
50
|
+
// Let's log to console for now as it's a CLI tool.
|
|
51
|
+
// console.log(chalk.gray(`Cleaned up ${removedCount} broken (0-byte) asset files.`));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Silently fail to avoid disrupting startup
|
|
56
|
+
// console.error('Asset cleanup failed:', error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { cleanupBrokenAssets };
|
package/src/utils/auth.js
CHANGED
|
@@ -2,7 +2,6 @@ const chalk = require('chalk');
|
|
|
2
2
|
const http = require('http');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
5
|
const net = require('net');
|
|
7
6
|
const sharedAuth = require('vibecodingmachine-core/src/auth/shared-auth-storage');
|
|
8
7
|
|
|
@@ -31,7 +30,37 @@ class CLIAuth {
|
|
|
31
30
|
* Check if authenticated (uses shared storage)
|
|
32
31
|
*/
|
|
33
32
|
async isAuthenticated() {
|
|
34
|
-
|
|
33
|
+
// First check if current token is valid
|
|
34
|
+
const isValid = await sharedAuth.isAuthenticated();
|
|
35
|
+
if (isValid) {
|
|
36
|
+
// Update user activity in database
|
|
37
|
+
await this._updateUserActivity();
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// If not valid, try to refresh
|
|
42
|
+
try {
|
|
43
|
+
const refreshToken = await sharedAuth.getRefreshToken();
|
|
44
|
+
if (refreshToken) {
|
|
45
|
+
// console.log(chalk.gray('Refreshing session...'));
|
|
46
|
+
const newTokens = await this._refreshTokens(refreshToken);
|
|
47
|
+
|
|
48
|
+
// Validate new ID token
|
|
49
|
+
await this._validateToken(newTokens.id_token);
|
|
50
|
+
|
|
51
|
+
// Save new tokens
|
|
52
|
+
await sharedAuth.saveToken(newTokens);
|
|
53
|
+
|
|
54
|
+
// Update user activity in database
|
|
55
|
+
await this._updateUserActivity();
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Refresh failed (token expired or revoked), user needs to login again
|
|
60
|
+
// console.log(chalk.gray('Session refresh failed, please login again.'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
35
64
|
}
|
|
36
65
|
|
|
37
66
|
/**
|
|
@@ -82,7 +111,7 @@ class CLIAuth {
|
|
|
82
111
|
res.on('end', () => {
|
|
83
112
|
if (res.statusCode === 200) {
|
|
84
113
|
const tokens = JSON.parse(data);
|
|
85
|
-
resolve(tokens
|
|
114
|
+
resolve(tokens); // Return full tokens object
|
|
86
115
|
} else {
|
|
87
116
|
reject(new Error(`Token exchange failed: ${res.statusCode} - ${data}`));
|
|
88
117
|
}
|
|
@@ -97,7 +126,53 @@ class CLIAuth {
|
|
|
97
126
|
req.end();
|
|
98
127
|
});
|
|
99
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Refresh tokens using refresh_token
|
|
131
|
+
*/
|
|
132
|
+
async _refreshTokens(refreshToken) {
|
|
133
|
+
const https = require('https');
|
|
134
|
+
|
|
135
|
+
const tokenEndpoint = `https://${COGNITO_DOMAIN}/oauth2/token`;
|
|
136
|
+
const postData = new URLSearchParams({
|
|
137
|
+
grant_type: 'refresh_token',
|
|
138
|
+
client_id: CLIENT_ID,
|
|
139
|
+
refresh_token: refreshToken
|
|
140
|
+
}).toString();
|
|
100
141
|
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
const options = {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
headers: {
|
|
146
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
147
|
+
'Content-Length': postData.length
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const req = https.request(tokenEndpoint, options, (res) => {
|
|
152
|
+
let data = '';
|
|
153
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
154
|
+
res.on('end', () => {
|
|
155
|
+
if (res.statusCode === 200) {
|
|
156
|
+
const tokens = JSON.parse(data);
|
|
157
|
+
// Cognito might not return a new refresh token, so we keep the old one if not provided
|
|
158
|
+
if (!tokens.refresh_token) {
|
|
159
|
+
tokens.refresh_token = refreshToken;
|
|
160
|
+
}
|
|
161
|
+
resolve(tokens);
|
|
162
|
+
} else {
|
|
163
|
+
reject(new Error(`Token refresh failed: ${res.statusCode} - ${data}`));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
req.on('error', (error) => {
|
|
169
|
+
reject(new Error(`Token refresh request failed: ${error.message}`));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
req.write(postData);
|
|
173
|
+
req.end();
|
|
174
|
+
});
|
|
175
|
+
}
|
|
101
176
|
/**
|
|
102
177
|
* Validate JWT token with signature verification
|
|
103
178
|
* @param {string} idToken - JWT token to validate
|
|
@@ -312,18 +387,25 @@ class CLIAuth {
|
|
|
312
387
|
try {
|
|
313
388
|
// Exchange code for tokens
|
|
314
389
|
console.log(chalk.gray('Exchanging authorization code for tokens...'));
|
|
315
|
-
|
|
390
|
+
// Exchange code for tokens
|
|
391
|
+
console.log(chalk.gray('Exchanging authorization code for tokens...'));
|
|
392
|
+
const tokens = await this._exchangeCodeForTokens(
|
|
316
393
|
code,
|
|
317
394
|
codeVerifier,
|
|
318
395
|
`http://localhost:${PORT}/callback`
|
|
319
396
|
);
|
|
320
397
|
|
|
398
|
+
const idToken = tokens.id_token;
|
|
399
|
+
|
|
321
400
|
// Validate token (signature + claims)
|
|
322
401
|
console.log(chalk.gray('Validating token...'));
|
|
323
402
|
await this._validateToken(idToken);
|
|
324
403
|
|
|
325
404
|
// Save token
|
|
326
|
-
await sharedAuth.saveToken(
|
|
405
|
+
await sharedAuth.saveToken(tokens);
|
|
406
|
+
|
|
407
|
+
// Register/update user in enhanced database
|
|
408
|
+
await this._registerUserInDatabase(idToken);
|
|
327
409
|
|
|
328
410
|
// Show success page
|
|
329
411
|
res.writeHead(200, {
|
|
@@ -428,7 +510,7 @@ class CLIAuth {
|
|
|
428
510
|
serverClosed = true;
|
|
429
511
|
server.close();
|
|
430
512
|
console.log(chalk.green('ā Authentication successful!\n'));
|
|
431
|
-
resolve(
|
|
513
|
+
resolve(tokens.id_token);
|
|
432
514
|
}
|
|
433
515
|
} catch (error) {
|
|
434
516
|
// Token exchange or validation failed
|
|
@@ -560,12 +642,16 @@ class CLIAuth {
|
|
|
560
642
|
|
|
561
643
|
// Exchange code for tokens
|
|
562
644
|
console.log(chalk.gray('Exchanging authorization code for tokens...'));
|
|
563
|
-
|
|
645
|
+
// Exchange code for tokens
|
|
646
|
+
console.log(chalk.gray('Exchanging authorization code for tokens...'));
|
|
647
|
+
const tokens = await this._exchangeCodeForTokens(
|
|
564
648
|
code,
|
|
565
649
|
codeVerifier,
|
|
566
650
|
'http://localhost:3000/callback'
|
|
567
651
|
);
|
|
568
652
|
|
|
653
|
+
const idToken = tokens.id_token;
|
|
654
|
+
|
|
569
655
|
// Validate JWT token
|
|
570
656
|
console.log(chalk.gray('Validating token...'));
|
|
571
657
|
try {
|
|
@@ -577,7 +663,10 @@ class CLIAuth {
|
|
|
577
663
|
}
|
|
578
664
|
|
|
579
665
|
// Save token using shared storage (only if validation passed)
|
|
580
|
-
await sharedAuth.saveToken(
|
|
666
|
+
await sharedAuth.saveToken(tokens);
|
|
667
|
+
|
|
668
|
+
// Register/update user in enhanced database
|
|
669
|
+
await this._registerUserInDatabase(idToken);
|
|
581
670
|
|
|
582
671
|
console.log(chalk.green('\nā Authentication successful!'));
|
|
583
672
|
return idToken;
|
|
@@ -614,6 +703,141 @@ class CLIAuth {
|
|
|
614
703
|
async activateLicense(licenseKey) {
|
|
615
704
|
return await sharedAuth.activateLicense(licenseKey);
|
|
616
705
|
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Register or update user in enhanced database system
|
|
709
|
+
*/
|
|
710
|
+
async _registerUserInDatabase(idToken) {
|
|
711
|
+
try {
|
|
712
|
+
// Decode JWT to get user info (without verification since we already validated)
|
|
713
|
+
const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
|
|
714
|
+
|
|
715
|
+
const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
|
|
716
|
+
const userDb = new UserDatabase();
|
|
717
|
+
|
|
718
|
+
const userInfo = {
|
|
719
|
+
email: payload.email,
|
|
720
|
+
name: payload.name || payload.email.split('@')[0],
|
|
721
|
+
cognitoId: payload.sub
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
// Register/update user
|
|
725
|
+
const user = await userDb.registerUser(userInfo);
|
|
726
|
+
|
|
727
|
+
// Register computer
|
|
728
|
+
await userDb.registerComputer(user.userId, {
|
|
729
|
+
interface: 'cli'
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
// Track login activity
|
|
733
|
+
await userDb.trackActivity(user.userId, {
|
|
734
|
+
interface: 'cli',
|
|
735
|
+
action: 'login',
|
|
736
|
+
duration: 0,
|
|
737
|
+
metadata: {
|
|
738
|
+
authMethod: 'cognito',
|
|
739
|
+
timestamp: Date.now()
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
} catch (error) {
|
|
744
|
+
// Don't fail authentication if database registration fails
|
|
745
|
+
console.error('Warning: Failed to register user in database:', error.message);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Update user activity in database
|
|
751
|
+
*/
|
|
752
|
+
async _updateUserActivity() {
|
|
753
|
+
try {
|
|
754
|
+
const token = await sharedAuth.getToken();
|
|
755
|
+
if (!token || !token.id_token) return;
|
|
756
|
+
|
|
757
|
+
// Decode JWT to get user info
|
|
758
|
+
const payload = JSON.parse(Buffer.from(token.id_token.split('.')[1], 'base64').toString());
|
|
759
|
+
|
|
760
|
+
const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
|
|
761
|
+
const userDb = new UserDatabase();
|
|
762
|
+
|
|
763
|
+
const userId = userDb.generateUserId(payload.email);
|
|
764
|
+
|
|
765
|
+
// Update last activity
|
|
766
|
+
await userDb.updateUserActivity(userId, {
|
|
767
|
+
lastActivity: Date.now()
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
} catch (error) {
|
|
771
|
+
// Silently fail - don't disrupt user experience
|
|
772
|
+
console.error('Warning: Failed to update user activity:', error.message);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Track CLI usage activity
|
|
778
|
+
*/
|
|
779
|
+
async trackCLIActivity(action, metadata = {}) {
|
|
780
|
+
try {
|
|
781
|
+
const token = await sharedAuth.getToken();
|
|
782
|
+
if (!token) return;
|
|
783
|
+
|
|
784
|
+
// Handle both string tokens and object tokens with id_token property
|
|
785
|
+
const idToken = typeof token === 'string' ? token : token.id_token;
|
|
786
|
+
if (!idToken) return;
|
|
787
|
+
|
|
788
|
+
const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
|
|
789
|
+
|
|
790
|
+
const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
|
|
791
|
+
const userDb = new UserDatabase();
|
|
792
|
+
|
|
793
|
+
const userId = userDb.generateUserId(payload.email);
|
|
794
|
+
|
|
795
|
+
await userDb.trackActivity(userId, {
|
|
796
|
+
interface: 'cli',
|
|
797
|
+
action,
|
|
798
|
+
duration: metadata.duration || 0,
|
|
799
|
+
metadata: {
|
|
800
|
+
...metadata,
|
|
801
|
+
timestamp: Date.now()
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
} catch (error) {
|
|
806
|
+
// Silently fail - don't disrupt user experience
|
|
807
|
+
console.error('Warning: Failed to track CLI activity:', error.message);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Get current user information
|
|
813
|
+
*/
|
|
814
|
+
async getCurrentUser() {
|
|
815
|
+
try {
|
|
816
|
+
const token = await sharedAuth.getToken();
|
|
817
|
+
if (!token) return null;
|
|
818
|
+
|
|
819
|
+
// Handle both string token and object with id_token
|
|
820
|
+
const idToken = typeof token === 'string' ? token : token.id_token;
|
|
821
|
+
if (!idToken) return null;
|
|
822
|
+
|
|
823
|
+
const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
|
|
824
|
+
|
|
825
|
+
const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
|
|
826
|
+
const userDb = new UserDatabase();
|
|
827
|
+
|
|
828
|
+
const userId = userDb.generateUserId(payload.email);
|
|
829
|
+
|
|
830
|
+
return {
|
|
831
|
+
userId,
|
|
832
|
+
email: payload.email,
|
|
833
|
+
name: payload.name || payload.email.split('@')[0],
|
|
834
|
+
cognitoId: payload.sub
|
|
835
|
+
};
|
|
836
|
+
} catch (error) {
|
|
837
|
+
console.error('Error getting current user:', error.message);
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
617
841
|
}
|
|
618
842
|
|
|
619
843
|
module.exports = new CLIAuth();
|