@equilateral_ai/mindmeld 3.2.0 → 3.3.1

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 (68) hide show
  1. package/README.md +4 -4
  2. package/hooks/README.md +46 -4
  3. package/hooks/pre-compact.js +87 -1
  4. package/hooks/session-end.js +292 -0
  5. package/hooks/session-start.js +292 -23
  6. package/package.json +4 -2
  7. package/scripts/auth-login.js +53 -0
  8. package/scripts/init-project.js +69 -375
  9. package/src/core/AuthManager.js +498 -0
  10. package/src/core/CrossReferenceEngine.js +624 -0
  11. package/src/core/DeprecationScheduler.js +183 -0
  12. package/src/core/LLMPatternDetector.js +218 -0
  13. package/src/core/RapportOrchestrator.js +186 -0
  14. package/src/core/RelevanceDetector.js +32 -2
  15. package/src/core/StandardLifecycle.js +244 -0
  16. package/src/core/StandardsIngestion.js +341 -28
  17. package/src/core/parsers/adrParser.js +479 -0
  18. package/src/core/parsers/cursorRulesParser.js +564 -0
  19. package/src/core/parsers/eslintParser.js +439 -0
  20. package/src/handlers/alerts/alertsAcknowledge.js +4 -3
  21. package/src/handlers/analytics/activitySummaryGet.js +235 -0
  22. package/src/handlers/analytics/coachingGet.js +361 -0
  23. package/src/handlers/analytics/developerScoreGet.js +207 -0
  24. package/src/handlers/collaborators/collaboratorAdd.js +4 -5
  25. package/src/handlers/collaborators/collaboratorInvite.js +6 -5
  26. package/src/handlers/collaborators/collaboratorList.js +3 -3
  27. package/src/handlers/collaborators/collaboratorRemove.js +5 -4
  28. package/src/handlers/correlations/correlationsDeveloperGet.js +12 -11
  29. package/src/handlers/correlations/correlationsGet.js +1 -1
  30. package/src/handlers/correlations/correlationsProjectGet.js +7 -6
  31. package/src/handlers/enterprise/enterpriseAuditGet.js +108 -0
  32. package/src/handlers/enterprise/enterpriseContributorsGet.js +85 -0
  33. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +53 -0
  34. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +77 -0
  35. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +71 -0
  36. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +87 -0
  37. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +122 -0
  38. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +77 -0
  39. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +138 -0
  40. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +89 -0
  41. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +90 -0
  42. package/src/handlers/github/githubConnectionStatus.js +1 -1
  43. package/src/handlers/github/githubDiscoverPatterns.js +264 -5
  44. package/src/handlers/github/githubOAuthCallback.js +14 -2
  45. package/src/handlers/github/githubOAuthStart.js +1 -1
  46. package/src/handlers/github/githubPatternsReview.js +1 -1
  47. package/src/handlers/github/githubReposList.js +1 -1
  48. package/src/handlers/helpers/auditLogger.js +201 -0
  49. package/src/handlers/helpers/index.js +19 -1
  50. package/src/handlers/helpers/lambdaWrapper.js +1 -1
  51. package/src/handlers/notifications/sendNotification.js +1 -1
  52. package/src/handlers/projects/projectCreate.js +28 -1
  53. package/src/handlers/projects/projectDelete.js +3 -3
  54. package/src/handlers/projects/projectUpdate.js +4 -5
  55. package/src/handlers/scheduled/analyzeCorrelations.js +3 -3
  56. package/src/handlers/scheduled/generateAlerts.js +1 -1
  57. package/src/handlers/standards/catalogGet.js +185 -0
  58. package/src/handlers/standards/catalogSync.js +120 -0
  59. package/src/handlers/standards/projectStandardsGet.js +135 -0
  60. package/src/handlers/standards/projectStandardsPut.js +131 -0
  61. package/src/handlers/standards/standardsAuditGet.js +65 -0
  62. package/src/handlers/standards/standardsParseUpload.js +153 -0
  63. package/src/handlers/standards/standardsRelevantPost.js +213 -0
  64. package/src/handlers/standards/standardsTransition.js +64 -0
  65. package/src/handlers/user/userSplashAck.js +91 -0
  66. package/src/handlers/user/userSplashGet.js +194 -0
  67. package/src/handlers/users/userProfilePut.js +77 -0
  68. package/src/index.js +75 -75
@@ -12,14 +12,10 @@
12
12
  */
13
13
 
14
14
  const fs = require('fs').promises;
15
- const fsSync = require('fs');
16
15
  const path = require('path');
17
- const http = require('http');
18
16
  const https = require('https');
19
- const crypto = require('crypto');
20
17
  const { exec } = require('child_process');
21
18
  const { promisify } = require('util');
22
- const os = require('os');
23
19
 
24
20
  const execAsync = promisify(exec);
25
21
 
@@ -27,374 +23,15 @@ const execAsync = promisify(exec);
27
23
  // Configuration
28
24
  // ============================================================================
29
25
 
26
+ const { AuthManager } = require('../src/core/AuthManager');
27
+ const authManager = new AuthManager();
28
+
30
29
  const CONFIG = {
31
- // Cognito settings (production)
32
- cognito: {
33
- domain: 'mindmeld-auth.auth.us-east-2.amazoncognito.com',
34
- clientId: '1al1vftuoh55a8n08ea6kr8heq',
35
- region: 'us-east-2'
36
- },
37
30
  // API settings (production)
38
31
  api: {
39
32
  baseUrl: 'https://api.mindmeld.dev'
40
- },
41
- // Local callback ports (fixed range for security)
42
- callbackPorts: [9876, 9877, 9878, 9879, 9880],
43
- // Auth file location
44
- authFile: path.join(os.homedir(), '.mindmeld', 'auth.json')
45
- };
46
-
47
- // ============================================================================
48
- // Authentication Module
49
- // ============================================================================
50
-
51
- /**
52
- * Load stored authentication
53
- */
54
- function loadAuth() {
55
- try {
56
- const data = fsSync.readFileSync(CONFIG.authFile, 'utf-8');
57
- return JSON.parse(data);
58
- } catch (error) {
59
- // Expected: auth file doesn't exist yet
60
- if (error.code !== 'ENOENT' && !(error instanceof SyntaxError)) {
61
- console.error('Unexpected error loading auth:', error.message);
62
- }
63
- return null;
64
- }
65
- }
66
-
67
- /**
68
- * Save authentication to disk
69
- */
70
- async function saveAuth(auth) {
71
- const dir = path.dirname(CONFIG.authFile);
72
- await fs.mkdir(dir, { recursive: true });
73
- await fs.writeFile(CONFIG.authFile, JSON.stringify(auth, null, 2));
74
- }
75
-
76
- /**
77
- * Clear stored authentication
78
- */
79
- async function clearAuth() {
80
- try {
81
- await fs.unlink(CONFIG.authFile);
82
- } catch (error) {
83
- // Expected: file doesn't exist
84
- if (error.code !== 'ENOENT') {
85
- console.error('Unexpected error clearing auth:', error.message);
86
- }
87
- }
88
- }
89
-
90
- /**
91
- * Decode JWT payload (without verification - Cognito already verified)
92
- */
93
- function decodeJwt(token) {
94
- const parts = token.split('.');
95
- if (parts.length !== 3) throw new Error('Invalid JWT');
96
- const payload = Buffer.from(parts[1], 'base64url').toString('utf-8');
97
- return JSON.parse(payload);
98
- }
99
-
100
- /**
101
- * Check if token is expired (with 5 min buffer)
102
- */
103
- function isTokenExpired(expiresAt) {
104
- return Date.now() > (expiresAt - 5 * 60 * 1000);
105
- }
106
-
107
- /**
108
- * Refresh tokens using refresh token
109
- */
110
- async function refreshTokens(refreshToken) {
111
- const tokenUrl = `https://${CONFIG.cognito.domain}/oauth2/token`;
112
-
113
- const params = new URLSearchParams({
114
- grant_type: 'refresh_token',
115
- client_id: CONFIG.cognito.clientId,
116
- refresh_token: refreshToken
117
- });
118
-
119
- return new Promise((resolve, reject) => {
120
- const req = https.request(tokenUrl, {
121
- method: 'POST',
122
- headers: {
123
- 'Content-Type': 'application/x-www-form-urlencoded'
124
- }
125
- }, (res) => {
126
- let data = '';
127
- res.on('data', chunk => data += chunk);
128
- res.on('end', () => {
129
- if (res.statusCode !== 200) {
130
- reject(new Error('Token refresh failed'));
131
- return;
132
- }
133
- try {
134
- const tokens = JSON.parse(data);
135
- const decoded = decodeJwt(tokens.id_token);
136
- resolve({
137
- id_token: tokens.id_token,
138
- access_token: tokens.access_token,
139
- refresh_token: refreshToken, // Cognito doesn't return new refresh token
140
- expires_at: Date.now() + (tokens.expires_in * 1000),
141
- email: decoded.email
142
- });
143
- } catch (e) {
144
- reject(e);
145
- }
146
- });
147
- });
148
-
149
- req.on('error', reject);
150
- req.write(params.toString());
151
- req.end();
152
- });
153
- }
154
-
155
- /**
156
- * Find an available port from the allowed list
157
- */
158
- async function findAvailablePort() {
159
- for (const port of CONFIG.callbackPorts) {
160
- const available = await new Promise((resolve) => {
161
- const server = http.createServer();
162
- server.listen(port, '127.0.0.1');
163
- server.on('listening', () => {
164
- server.close();
165
- resolve(true);
166
- });
167
- server.on('error', () => {
168
- resolve(false);
169
- });
170
- });
171
- if (available) return port;
172
- }
173
- throw new Error('No available ports for OAuth callback');
174
- }
175
-
176
- /**
177
- * Start local server to receive OAuth callback
178
- */
179
- function startCallbackServer(port, expectedState) {
180
- return new Promise((resolve, reject) => {
181
- const server = http.createServer((req, res) => {
182
- const url = new URL(req.url, `http://localhost:${port}`);
183
-
184
- if (url.pathname === '/callback') {
185
- const code = url.searchParams.get('code');
186
- const state = url.searchParams.get('state');
187
- const error = url.searchParams.get('error');
188
-
189
- if (error) {
190
- res.writeHead(200, { 'Content-Type': 'text/html' });
191
- res.end(`
192
- <html><body style="font-family: system-ui; padding: 40px; text-align: center;">
193
- <h1>Authentication Failed</h1>
194
- <p>Error: ${error}</p>
195
- <p>You can close this window.</p>
196
- </body></html>
197
- `);
198
- server.close();
199
- reject(new Error(`OAuth error: ${error}`));
200
- return;
201
- }
202
-
203
- if (state !== expectedState) {
204
- res.writeHead(400, { 'Content-Type': 'text/html' });
205
- res.end(`
206
- <html><body style="font-family: system-ui; padding: 40px; text-align: center;">
207
- <h1>Authentication Failed</h1>
208
- <p>Invalid state parameter (possible CSRF attack)</p>
209
- <p>You can close this window.</p>
210
- </body></html>
211
- `);
212
- server.close();
213
- reject(new Error('Invalid state parameter'));
214
- return;
215
- }
216
-
217
- res.writeHead(200, { 'Content-Type': 'text/html' });
218
- res.end(`
219
- <html><body style="font-family: system-ui; padding: 40px; text-align: center;">
220
- <h1>Authentication Successful!</h1>
221
- <p>You can close this window and return to your terminal.</p>
222
- </body></html>
223
- `);
224
- server.close();
225
- resolve(code);
226
- } else {
227
- res.writeHead(404);
228
- res.end('Not found');
229
- }
230
- });
231
-
232
- server.listen(port, '127.0.0.1');
233
-
234
- // Timeout after 5 minutes
235
- setTimeout(() => {
236
- server.close();
237
- reject(new Error('Authentication timed out'));
238
- }, 5 * 60 * 1000);
239
- });
240
- }
241
-
242
- /**
243
- * Exchange authorization code for tokens
244
- */
245
- async function exchangeCodeForTokens(code, codeVerifier, port) {
246
- const tokenUrl = `https://${CONFIG.cognito.domain}/oauth2/token`;
247
- const redirectUri = `http://localhost:${port}/callback`;
248
-
249
- const params = new URLSearchParams({
250
- grant_type: 'authorization_code',
251
- client_id: CONFIG.cognito.clientId,
252
- code: code,
253
- redirect_uri: redirectUri,
254
- code_verifier: codeVerifier
255
- });
256
-
257
- return new Promise((resolve, reject) => {
258
- const req = https.request(tokenUrl, {
259
- method: 'POST',
260
- headers: {
261
- 'Content-Type': 'application/x-www-form-urlencoded'
262
- }
263
- }, (res) => {
264
- let data = '';
265
- res.on('data', chunk => data += chunk);
266
- res.on('end', () => {
267
- if (res.statusCode !== 200) {
268
- reject(new Error(`Token exchange failed: ${data}`));
269
- return;
270
- }
271
- try {
272
- const tokens = JSON.parse(data);
273
- const decoded = decodeJwt(tokens.id_token);
274
- resolve({
275
- id_token: tokens.id_token,
276
- access_token: tokens.access_token,
277
- refresh_token: tokens.refresh_token,
278
- expires_at: Date.now() + (tokens.expires_in * 1000),
279
- email: decoded.email
280
- });
281
- } catch (e) {
282
- reject(e);
283
- }
284
- });
285
- });
286
-
287
- req.on('error', reject);
288
- req.write(params.toString());
289
- req.end();
290
- });
291
- }
292
-
293
- /**
294
- * Open browser for authentication
295
- */
296
- async function openBrowser(url) {
297
- const platform = process.platform;
298
- let command;
299
-
300
- if (platform === 'darwin') {
301
- command = `open "${url}"`;
302
- } else if (platform === 'win32') {
303
- command = `start "" "${url}"`;
304
- } else {
305
- command = `xdg-open "${url}"`;
306
- }
307
-
308
- try {
309
- await execAsync(command);
310
- return true;
311
- } catch (error) {
312
- // Expected: browser command not available on some systems
313
- if (error.code !== 'ENOENT' && !error.message.includes('spawn')) {
314
- console.error('Unexpected error opening browser:', error.message);
315
- }
316
- return false;
317
- }
318
- }
319
-
320
- /**
321
- * Full browser authentication flow (PKCE)
322
- */
323
- async function browserAuth() {
324
- // Generate PKCE challenge
325
- const codeVerifier = crypto.randomBytes(32).toString('base64url');
326
- const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
327
- const state = crypto.randomBytes(16).toString('hex');
328
-
329
- // Find available port
330
- const port = await findAvailablePort();
331
- const redirectUri = `http://localhost:${port}/callback`;
332
-
333
- // Build Cognito authorization URL
334
- const authUrl = `https://${CONFIG.cognito.domain}/login?` + new URLSearchParams({
335
- client_id: CONFIG.cognito.clientId,
336
- response_type: 'code',
337
- scope: 'openid email profile',
338
- redirect_uri: redirectUri,
339
- state: state,
340
- code_challenge: codeChallenge,
341
- code_challenge_method: 'S256'
342
- }).toString();
343
-
344
- // Start callback server
345
- const codePromise = startCallbackServer(port, state);
346
-
347
- // Open browser
348
- console.log('\n🔐 Opening browser for authentication...');
349
- console.log(` Callback server listening on port ${port}`);
350
-
351
- const opened = await openBrowser(authUrl);
352
-
353
- if (!opened) {
354
- console.log('\n Could not open browser automatically.');
355
33
  }
356
-
357
- console.log('\n If login page doesn\'t appear, paste this URL in your browser:\n');
358
- console.log(` ${authUrl}\n`);
359
-
360
- console.log(' Waiting for authentication (5 minute timeout)...\n');
361
-
362
- // Wait for callback
363
- const code = await codePromise;
364
-
365
- // Exchange code for tokens
366
- const tokens = await exchangeCodeForTokens(code, codeVerifier, port);
367
- await saveAuth(tokens);
368
-
369
- return tokens;
370
- }
371
-
372
- /**
373
- * Ensure user is authenticated (main entry point)
374
- */
375
- async function ensureAuth() {
376
- let auth = loadAuth();
377
-
378
- // Check if we have valid tokens
379
- if (auth?.id_token && !isTokenExpired(auth.expires_at)) {
380
- return auth;
381
- }
382
-
383
- // Try silent refresh
384
- if (auth?.refresh_token) {
385
- try {
386
- console.log('🔄 Refreshing authentication...');
387
- const newAuth = await refreshTokens(auth.refresh_token);
388
- await saveAuth(newAuth);
389
- return newAuth;
390
- } catch (e) {
391
- console.log(' Refresh failed, need to re-authenticate.\n');
392
- }
393
- }
394
-
395
- // Full browser auth
396
- return await browserAuth();
397
- }
34
+ };
398
35
 
399
36
  // ============================================================================
400
37
  // API Module
@@ -500,7 +137,7 @@ function parseArgs(args) {
500
137
  else if (arg === '--path' && args[i + 1]) { parsed.projectPath = args[++i]; }
501
138
  else if (arg === '--since' && args[i + 1]) { parsed.since = args[++i]; }
502
139
  else if (arg === '--commits' && args[i + 1]) { parsed.commits = parseInt(args[++i]); }
503
- else if (['init', 'inject', 'harvest', 'logout', 'login', 'status'].includes(arg)) parsed.command = arg;
140
+ else if (!parsed.command && ['init', 'inject', 'harvest', 'logout', 'login', 'status', 'standards', 'open'].includes(arg)) parsed.command = arg;
504
141
  else if (!arg.startsWith('-') && !parsed.command) parsed.command = arg;
505
142
  else if (!arg.startsWith('-') && !parsed.projectPath) parsed.projectPath = arg;
506
143
  }
@@ -521,6 +158,8 @@ Commands:
521
158
  init [path] Initialize project for MindMeld (requires login)
522
159
  inject Generate context-aware standards for any AI tool
523
160
  harvest Manually capture patterns from recent git history
161
+ standards Configure which standards apply (opens browser)
162
+ open [page] Open web app (dashboard, standards, review, settings, projects, knowledge, audit, reports)
524
163
  login Authenticate with MindMeld
525
164
  logout Clear stored authentication
526
165
  status Show current authentication status
@@ -562,7 +201,7 @@ async function initProject(projectPath) {
562
201
 
563
202
  // 2. Authenticate
564
203
  console.log('\n');
565
- const auth = await ensureAuth();
204
+ const auth = await authManager.ensureAuth();
566
205
  console.log(`✅ Authenticated as ${auth.email}\n`);
567
206
 
568
207
  // 2. Check subscription
@@ -596,7 +235,7 @@ async function initProject(projectPath) {
596
235
 
597
236
  if (checkoutResponse.success && checkoutResponse.data?.data?.url) {
598
237
  console.log('\n🛒 Opening checkout...');
599
- await openBrowser(checkoutResponse.data.data.url);
238
+ await authManager.openBrowser(checkoutResponse.data.data.url);
600
239
  tier = await pollForSubscription(auth.id_token);
601
240
  } else {
602
241
  console.log(`\nâš ī¸ Could not create checkout: ${checkoutResponse.error}`);
@@ -653,6 +292,15 @@ async function initProject(projectPath) {
653
292
  // Project already exists, that's fine
654
293
  backendProjectId = projectId;
655
294
  console.log(` Project already registered: ${projectId}\n`);
295
+ } else if (projectResponse.status === 402) {
296
+ // Project limit exceeded for tier
297
+ console.error(`\nâš ī¸ ${projectResponse.error || 'Project limit reached for your plan.'}`);
298
+ console.error(' Upgrade at https://app.mindmeld.dev/subscriptions\n');
299
+ const upgrade = await promptYesNo(' Open upgrade page? (y/n): ');
300
+ if (upgrade) {
301
+ await authManager.openApp('subscriptions');
302
+ }
303
+ process.exit(1);
656
304
  } else {
657
305
  console.log(` Warning: Could not register project: ${projectResponse.error}`);
658
306
  console.log(' Continuing with local-only setup.\n');
@@ -846,10 +494,12 @@ async function configureClaudeHooks(projectPath) {
846
494
  const packageRoot = path.resolve(__dirname, '..');
847
495
  const sessionStartHook = path.join(packageRoot, 'hooks', 'session-start.js');
848
496
  const preCompactHook = path.join(packageRoot, 'hooks', 'pre-compact.js');
497
+ const sessionEndHook = path.join(packageRoot, 'hooks', 'session-end.js');
849
498
 
850
499
  try {
851
500
  await fs.access(sessionStartHook);
852
501
  await fs.access(preCompactHook);
502
+ await fs.access(sessionEndHook);
853
503
  } catch (error) {
854
504
  // Expected: hooks not found in package
855
505
  if (error.code !== 'ENOENT') {
@@ -874,6 +524,13 @@ async function configureClaudeHooks(projectPath) {
874
524
  command: `node "${preCompactHook}"`,
875
525
  timeout: 30
876
526
  }]
527
+ }],
528
+ SessionEnd: [{
529
+ hooks: [{
530
+ type: 'command',
531
+ command: `node "${sessionEndHook}"`,
532
+ timeout: 5
533
+ }]
877
534
  }]
878
535
  };
879
536
 
@@ -901,8 +558,11 @@ async function configureClaudeHooks(projectPath) {
901
558
  const hasPreCompact = (settings.hooks.PreCompact || []).some(h =>
902
559
  h.hooks?.some(hk => hk.command?.includes('mindmeld') || hk.command?.includes('pre-compact'))
903
560
  );
561
+ const hasSessionEnd = (settings.hooks.SessionEnd || []).some(h =>
562
+ h.hooks?.some(hk => hk.command?.includes('mindmeld') || hk.command?.includes('session-end'))
563
+ );
904
564
 
905
- if (hasSessionStart && hasPreCompact) {
565
+ if (hasSessionStart && hasPreCompact && hasSessionEnd) {
906
566
  console.log('â„šī¸ Claude Code hooks already configured');
907
567
  return;
908
568
  }
@@ -919,12 +579,19 @@ async function configureClaudeHooks(projectPath) {
919
579
  ...mindmeldHooks.PreCompact
920
580
  ];
921
581
  }
582
+ if (!hasSessionEnd) {
583
+ settings.hooks.SessionEnd = [
584
+ ...(settings.hooks.SessionEnd || []),
585
+ ...mindmeldHooks.SessionEnd
586
+ ];
587
+ }
922
588
 
923
589
  await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
924
590
 
925
591
  console.log('✅ Claude Code hooks configured');
926
592
  console.log(` SessionStart: ${sessionStartHook}`);
927
593
  console.log(` PreCompact: ${preCompactHook}`);
594
+ console.log(` SessionEnd: ${sessionEndHook}`);
928
595
  }
929
596
 
930
597
  // ============================================================================
@@ -932,7 +599,7 @@ async function configureClaudeHooks(projectPath) {
932
599
  // ============================================================================
933
600
 
934
601
  async function showStatus() {
935
- const auth = loadAuth();
602
+ const auth = authManager.loadAuth();
936
603
 
937
604
  console.log('\nđŸŽ¯ MindMeld Status\n');
938
605
 
@@ -944,22 +611,39 @@ async function showStatus() {
944
611
 
945
612
  console.log(` Email: ${auth.email}`);
946
613
  console.log(` Token expires: ${new Date(auth.expires_at).toLocaleString()}`);
947
- console.log(` Token status: ${isTokenExpired(auth.expires_at) ? 'Expired' : 'Valid'}`);
614
+ console.log(` Token status: ${authManager.isTokenExpired(auth.expires_at) ? 'Expired' : 'Valid'}`);
948
615
  console.log(` Refresh token: ${auth.refresh_token ? 'Present' : 'Missing'}`);
949
616
  console.log('');
950
617
  }
951
618
 
952
619
  async function logout() {
953
- await clearAuth();
620
+ await authManager.clearAuth();
954
621
  console.log('\n✅ Logged out successfully.\n');
955
622
  }
956
623
 
957
624
  async function login() {
958
625
  console.log('\nđŸŽ¯ MindMeld Login\n');
959
- const auth = await browserAuth();
626
+ const auth = await authManager.browserAuth();
960
627
  console.log(`\n✅ Logged in as ${auth.email}\n`);
961
628
  }
962
629
 
630
+ async function openWebApp(page) {
631
+ const validPages = ['dashboard', 'standards', 'review', 'settings', 'projects', 'knowledge', 'audit', 'reports'];
632
+ if (!validPages.includes(page)) {
633
+ console.error(`\nUnknown page: ${page}`);
634
+ console.error(`Valid pages: ${validPages.join(', ')}\n`);
635
+ process.exit(1);
636
+ }
637
+
638
+ const url = `${authManager.getAppBaseUrl()}/${page}`;
639
+ console.log(`\n🌐 Opening ${url}\n`);
640
+
641
+ const opened = await authManager.openApp(page);
642
+ if (!opened) {
643
+ console.log(`Could not open browser. Visit: ${url}\n`);
644
+ }
645
+ }
646
+
963
647
  async function promptYesNo(question) {
964
648
  process.stdout.write(question);
965
649
 
@@ -1029,6 +713,16 @@ if (args.command === 'init') {
1029
713
  console.error('\n❌ Error:', error.message);
1030
714
  process.exit(1);
1031
715
  });
716
+ } else if (args.command === 'standards') {
717
+ // Delegate to standards.js
718
+ require('./standards');
719
+ } else if (args.command === 'open') {
720
+ openWebApp(args.projectPath || 'dashboard')
721
+ .then(() => process.exit(0))
722
+ .catch(error => {
723
+ console.error('\n❌ Error:', error.message);
724
+ process.exit(1);
725
+ });
1032
726
  } else {
1033
727
  console.error(`Unknown command: ${args.command}`);
1034
728
  console.error('Run "mindmeld --help" for usage.');