@vee-stack/delta-cli 2.0.5 → 2.0.7

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.
@@ -141,120 +141,53 @@ export async function startOAuthFlow(method = 'oauth') {
141
141
  // Get API URL from environment or use default
142
142
  const apiUrl = process.env.DELTA_API_URL || 'http://localhost:3000';
143
143
  const { verifier, challenge } = generatePKCE();
144
- const redirectUri = 'http://localhost:3456/callback';
145
144
  const state = crypto.randomBytes(16).toString('hex');
146
- // Build OAuth URL
147
- const oauthUrl = new URL(`${apiUrl}/api/auth/authorize`);
148
- oauthUrl.searchParams.set('response_type', 'code');
149
- oauthUrl.searchParams.set('client_id', 'delta-cli');
150
- oauthUrl.searchParams.set('redirect_uri', redirectUri);
151
- oauthUrl.searchParams.set('code_challenge', challenge);
152
- oauthUrl.searchParams.set('code_challenge_method', 'S256');
153
- oauthUrl.searchParams.set('state', state);
154
- oauthUrl.searchParams.set('scope', 'read write analyze');
155
- if (method === 'github') {
156
- oauthUrl.searchParams.set('provider', 'github');
157
- }
158
- else if (method === 'google') {
159
- oauthUrl.searchParams.set('provider', 'google');
160
- }
161
- stopSpinner(true, 'OAuth server ready');
162
- // Open browser
163
- printInfo(`Opening browser for authentication...`);
164
- printInfo(`URL: ${oauthUrl.toString().substring(0, 80)}...`);
165
- await open(oauthUrl.toString());
166
- // Start local server to capture callback
167
- const authCode = await new Promise((resolve, reject) => {
145
+ // Start local server FIRST to capture callback and get actual port
146
+ const { authCode, redirectUri } = await new Promise((resolve, reject) => {
168
147
  const server = createServer(async (req, res) => {
169
- // Get actual port from server address
170
- const address = server.address();
171
- const actualPort = typeof address === 'object' && address ? address.port : 3000;
172
- const url = new URL(req.url || '/', `http://localhost:${actualPort}`);
148
+ const url = new URL(req.url || '/', `http://localhost:${redirectPort}`);
173
149
  if (url.pathname === '/callback') {
174
150
  const code = url.searchParams.get('code');
175
151
  const returnedState = url.searchParams.get('state');
176
152
  const error = url.searchParams.get('error');
177
153
  if (error) {
178
154
  res.writeHead(400, { 'Content-Type': 'text/html' });
179
- res.end(`
180
- <html>
181
- <body style="font-family: system-ui; text-align: center; padding: 50px;">
182
- <h1 style="color: #e74c3c;">❌ Authentication Failed</h1>
183
- <p>${error}</p>
184
- <p>You can close this window.</p>
185
- </body>
186
- </html>
187
- `);
155
+ res.end(`<html><body><h1>❌ Authentication Failed</h1><p>${error}</p></body></html>`);
188
156
  reject(new Error(error));
189
157
  server.close();
190
158
  return;
191
159
  }
192
160
  if (!code || returnedState !== state) {
193
161
  res.writeHead(400, { 'Content-Type': 'text/html' });
194
- res.end(`
195
- <html>
196
- <body style="font-family: system-ui; text-align: center; padding: 50px;">
197
- <h1 style="color: #e74c3c;">❌ Invalid Response</h1>
198
- <p>You can close this window.</p>
199
- </body>
200
- </html>
201
- `);
162
+ res.end(`<html><body><h1>❌ Invalid Response</h1></body></html>`);
202
163
  reject(new Error('Invalid OAuth response'));
203
164
  server.close();
204
165
  return;
205
166
  }
206
167
  res.writeHead(200, { 'Content-Type': 'text/html' });
207
- res.end(`
208
- <html>
209
- <head>
210
- <style>
211
- body {
212
- font-family: system-ui, -apple-system, sans-serif;
213
- text-align: center;
214
- padding: 50px;
215
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
216
- color: white;
217
- min-height: 100vh;
218
- display: flex;
219
- align-items: center;
220
- justify-content: center;
221
- }
222
- .container {
223
- background: rgba(255,255,255,0.1);
224
- backdrop-filter: blur(10px);
225
- padding: 40px;
226
- border-radius: 20px;
227
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
228
- }
229
- h1 { margin: 0 0 20px; font-size: 48px; }
230
- .check { font-size: 64px; margin-bottom: 20px; }
231
- p { font-size: 18px; opacity: 0.9; }
232
- </style>
233
- </head>
234
- <body>
235
- <div class="container">
236
- <div class="check">✓</div>
237
- <h1>Successfully Signed In!</h1>
238
- <p>You can close this window and return to the terminal.</p>
239
- </div>
240
- </body>
241
- </html>
242
- `);
243
- resolve(code);
168
+ res.end(`<html><body style="text-align:center;padding:50px;"><h1>✓ Successfully Signed In!</h1><p>You can close this window.</p></body></html>`);
169
+ const address = server.address();
170
+ const actualPort = typeof address === 'object' && address ? address.port : 3456;
171
+ resolve({ authCode: code, redirectUri: `http://localhost:${actualPort}/callback` });
244
172
  server.close();
245
173
  }
246
174
  });
247
- // Try to listen on port 3456, or let OS assign available port if busy
175
+ let redirectPort = 3456;
176
+ // Try to listen on port 3456
248
177
  server.listen(3456, '127.0.0.1', () => {
249
- const address = server.address();
250
- const actualPort = typeof address === 'object' && address ? address.port : 3456;
251
- console.log(chalk.dim(` Waiting for authentication on port ${actualPort}...`));
178
+ console.log(chalk.dim(` Waiting for authentication on port ${redirectPort}...`));
252
179
  });
253
180
  // Handle port in use error by trying random port
254
181
  server.on('error', (err) => {
255
182
  if (err.code === 'EADDRINUSE') {
256
183
  printWarning('Port 3456 is busy, trying alternative port...');
257
- server.listen(0, '127.0.0.1'); // Let OS assign available port
184
+ redirectPort = 0; // Let OS assign
185
+ server.listen(0, '127.0.0.1', () => {
186
+ const address = server.address();
187
+ const actualPort = typeof address === 'object' && address ? address.port : 3456;
188
+ redirectPort = actualPort;
189
+ console.log(chalk.dim(` Waiting for authentication on port ${actualPort}...`));
190
+ });
258
191
  }
259
192
  else {
260
193
  reject(err);
@@ -266,6 +199,27 @@ export async function startOAuthFlow(method = 'oauth') {
266
199
  reject(new Error('Authentication timeout'));
267
200
  }, 5 * 60 * 1000);
268
201
  });
202
+ // NOW build OAuth URL with correct redirect_uri
203
+ const oauthUrl = new URL(`${apiUrl}/api/auth/authorize`);
204
+ oauthUrl.searchParams.set('response_type', 'code');
205
+ oauthUrl.searchParams.set('client_id', 'delta-cli');
206
+ oauthUrl.searchParams.set('redirect_uri', redirectUri);
207
+ oauthUrl.searchParams.set('code_challenge', challenge);
208
+ oauthUrl.searchParams.set('code_challenge_method', 'S256');
209
+ oauthUrl.searchParams.set('state', state);
210
+ oauthUrl.searchParams.set('scope', 'read write analyze');
211
+ if (method === 'github') {
212
+ oauthUrl.searchParams.set('provider', 'github');
213
+ }
214
+ else if (method === 'google') {
215
+ oauthUrl.searchParams.set('provider', 'google');
216
+ }
217
+ stopSpinner(true, 'OAuth server ready');
218
+ // Open browser with correct URL
219
+ printInfo(`Opening browser for authentication...`);
220
+ printInfo(`Full URL: ${oauthUrl.toString()}`);
221
+ await open(oauthUrl.toString());
222
+ // Wait for authCode (already resolved above)
269
223
  // Exchange code for tokens
270
224
  startSpinner('Exchanging authorization code...');
271
225
  const tokenResponse = await fetch(`${apiUrl}/api/auth/token`, {
@@ -22,6 +22,26 @@ function extractApiErrorPayload(payload) {
22
22
  }
23
23
  export async function analyzeCommand(projectPath, options) {
24
24
  const targetPath = path.resolve(projectPath);
25
+ // Check authentication FIRST if upload is requested
26
+ if (options.upload) {
27
+ printInfo('🔐 Checking authentication...');
28
+ const config = await loadConfig();
29
+ const pat = await SecureTokenStore.getAccessToken();
30
+ if (!pat) {
31
+ console.error();
32
+ console.error(chalk.red('❌ Error: You must be logged in to upload reports.'));
33
+ console.error();
34
+ console.error(chalk.yellow('Please authenticate first:'));
35
+ console.error(chalk.cyan(' delta login'));
36
+ console.error();
37
+ console.error(chalk.dim('Or use --upload with a valid PAT:'));
38
+ console.error(chalk.cyan(' delta analyze . --upload --token <your-pat>'));
39
+ console.error();
40
+ process.exit(1);
41
+ }
42
+ printSuccess('✓ Authenticated');
43
+ console.log();
44
+ }
25
45
  printInfo('🔍 Starting Delta Analysis Engine...');
26
46
  printDivider();
27
47
  console.log(`${icons.folder} Project: ${chalk.cyan(targetPath)}`);
@@ -153,18 +173,25 @@ export async function analyzeCommand(projectPath, options) {
153
173
  printSuccess('✨ No issues found! Great job!');
154
174
  }
155
175
  console.log();
156
- // Save report to file
157
- const outputDir = path.resolve(options.output);
158
- await fs.mkdir(outputDir, { recursive: true });
159
- const reportFile = path.join(outputDir, `report-${Date.now()}.json`);
160
- await fs.writeFile(reportFile, JSON.stringify(report, null, 2));
161
- printInfo(`${icons.chart} Report saved: ${chalk.cyan(reportFile)}`);
162
- console.log();
176
+ // Save report to file ONLY if not uploading
177
+ if (!options.upload) {
178
+ const outputDir = path.resolve(options.output);
179
+ await fs.mkdir(outputDir, { recursive: true });
180
+ const reportFile = path.join(outputDir, `report-${Date.now()}.json`);
181
+ await fs.writeFile(reportFile, JSON.stringify(report, null, 2));
182
+ printInfo(`${icons.chart} Report saved: ${chalk.cyan(reportFile)}`);
183
+ console.log();
184
+ }
185
+ else {
186
+ printInfo(`${icons.chart} Report will be available on Delta Cloud Dashboard`);
187
+ console.log();
188
+ }
163
189
  // Upload to cloud if requested
164
190
  if (options.upload) {
165
191
  console.log(`${icons.rocket} Uploading to Delta Cloud...`);
166
192
  await uploadReport(report);
167
193
  }
194
+ process.exit(0);
168
195
  }
169
196
  async function uploadReport(report) {
170
197
  const config = await loadConfig();
@@ -44,7 +44,7 @@ export const authCommands = {
44
44
  console.log();
45
45
  printCommandHint('delta whoami', 'to see account details');
46
46
  printCommandHint('delta analyze', 'to start analyzing code');
47
- return;
47
+ process.exit(0);
48
48
  }
49
49
  // PAT Token flow (when token is provided)
50
50
  // Validate token format
@@ -102,6 +102,7 @@ export const authCommands = {
102
102
  console.log();
103
103
  printCommandHint('delta whoami', 'to see full account details');
104
104
  printCommandHint('delta analyze', 'to start analyzing code');
105
+ process.exit(0);
105
106
  }
106
107
  catch (error) {
107
108
  stopSpinner(false, 'Authentication failed');
@@ -13,7 +13,7 @@ export async function configCommand(options) {
13
13
  else {
14
14
  console.log(`${options.get}: (not set)`);
15
15
  }
16
- return;
16
+ process.exit(0);
17
17
  }
18
18
  if (options.set) {
19
19
  const [key, value] = options.set.split('=');
@@ -28,7 +28,7 @@ export async function configCommand(options) {
28
28
  };
29
29
  await saveConfig(newConfig);
30
30
  console.log(`✅ Set ${key} = ${value}`);
31
- return;
31
+ process.exit(0);
32
32
  }
33
33
  if (options.list || (!options.get && !options.set)) {
34
34
  console.log('🔧 Delta CLI Configuration');
@@ -45,6 +45,7 @@ export async function configCommand(options) {
45
45
  if (config.lastUsedAt) {
46
46
  console.log(` Last used: ${new Date(config.lastUsedAt).toLocaleString()}`);
47
47
  }
48
+ process.exit(0);
48
49
  }
49
50
  }
50
51
  //# sourceMappingURL=config.js.map
@@ -9,7 +9,7 @@ export async function logoutCommand() {
9
9
  const hasToken = await SecureTokenStore.hasTokens();
10
10
  if (!hasToken) {
11
11
  console.log('ℹ️ Not currently authenticated');
12
- return;
12
+ process.exit(0);
13
13
  }
14
14
  // Clear tokens from keychain and remove config file
15
15
  await SecureTokenStore.clearTokens();
@@ -21,6 +21,7 @@ export async function logoutCommand() {
21
21
  }
22
22
  console.log('✅ Successfully logged out');
23
23
  console.log(' Authentication data removed');
24
+ process.exit(0);
24
25
  }
25
26
  catch (error) {
26
27
  console.error('❌ Error during logout:', error instanceof Error ? error.message : String(error));
@@ -51,6 +51,7 @@ export async function whoamiCommand(_options) {
51
51
  console.log();
52
52
  printCommandHint('delta analyze', 'to run a code analysis');
53
53
  printCommandHint('delta status', 'to check project status');
54
+ process.exit(0);
54
55
  }
55
56
  catch (error) {
56
57
  stopSpinner(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vee-stack/delta-cli",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "Delta CLI v2 - Next-gen terminal interface with React Ink",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",