@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.
- package/README.md +4 -4
- package/hooks/README.md +46 -4
- package/hooks/pre-compact.js +87 -1
- package/hooks/session-end.js +292 -0
- package/hooks/session-start.js +292 -23
- package/package.json +4 -2
- package/scripts/auth-login.js +53 -0
- package/scripts/init-project.js +69 -375
- package/src/core/AuthManager.js +498 -0
- package/src/core/CrossReferenceEngine.js +624 -0
- package/src/core/DeprecationScheduler.js +183 -0
- package/src/core/LLMPatternDetector.js +218 -0
- package/src/core/RapportOrchestrator.js +186 -0
- package/src/core/RelevanceDetector.js +32 -2
- package/src/core/StandardLifecycle.js +244 -0
- package/src/core/StandardsIngestion.js +341 -28
- package/src/core/parsers/adrParser.js +479 -0
- package/src/core/parsers/cursorRulesParser.js +564 -0
- package/src/core/parsers/eslintParser.js +439 -0
- package/src/handlers/alerts/alertsAcknowledge.js +4 -3
- package/src/handlers/analytics/activitySummaryGet.js +235 -0
- package/src/handlers/analytics/coachingGet.js +361 -0
- package/src/handlers/analytics/developerScoreGet.js +207 -0
- package/src/handlers/collaborators/collaboratorAdd.js +4 -5
- package/src/handlers/collaborators/collaboratorInvite.js +6 -5
- package/src/handlers/collaborators/collaboratorList.js +3 -3
- package/src/handlers/collaborators/collaboratorRemove.js +5 -4
- package/src/handlers/correlations/correlationsDeveloperGet.js +12 -11
- package/src/handlers/correlations/correlationsGet.js +1 -1
- package/src/handlers/correlations/correlationsProjectGet.js +7 -6
- package/src/handlers/enterprise/enterpriseAuditGet.js +108 -0
- package/src/handlers/enterprise/enterpriseContributorsGet.js +85 -0
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +53 -0
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +77 -0
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +71 -0
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +87 -0
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +122 -0
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +77 -0
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +138 -0
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +89 -0
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +90 -0
- package/src/handlers/github/githubConnectionStatus.js +1 -1
- package/src/handlers/github/githubDiscoverPatterns.js +264 -5
- package/src/handlers/github/githubOAuthCallback.js +14 -2
- package/src/handlers/github/githubOAuthStart.js +1 -1
- package/src/handlers/github/githubPatternsReview.js +1 -1
- package/src/handlers/github/githubReposList.js +1 -1
- package/src/handlers/helpers/auditLogger.js +201 -0
- package/src/handlers/helpers/index.js +19 -1
- package/src/handlers/helpers/lambdaWrapper.js +1 -1
- package/src/handlers/notifications/sendNotification.js +1 -1
- package/src/handlers/projects/projectCreate.js +28 -1
- package/src/handlers/projects/projectDelete.js +3 -3
- package/src/handlers/projects/projectUpdate.js +4 -5
- package/src/handlers/scheduled/analyzeCorrelations.js +3 -3
- package/src/handlers/scheduled/generateAlerts.js +1 -1
- package/src/handlers/standards/catalogGet.js +185 -0
- package/src/handlers/standards/catalogSync.js +120 -0
- package/src/handlers/standards/projectStandardsGet.js +135 -0
- package/src/handlers/standards/projectStandardsPut.js +131 -0
- package/src/handlers/standards/standardsAuditGet.js +65 -0
- package/src/handlers/standards/standardsParseUpload.js +153 -0
- package/src/handlers/standards/standardsRelevantPost.js +213 -0
- package/src/handlers/standards/standardsTransition.js +64 -0
- package/src/handlers/user/userSplashAck.js +91 -0
- package/src/handlers/user/userSplashGet.js +194 -0
- package/src/handlers/users/userProfilePut.js +77 -0
- package/src/index.js +75 -75
package/scripts/init-project.js
CHANGED
|
@@ -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.');
|