@equilateral_ai/mindmeld 3.0.0 → 3.1.2
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/package.json +1 -1
- package/scripts/init-project.js +657 -124
package/package.json
CHANGED
package/scripts/init-project.js
CHANGED
|
@@ -1,28 +1,475 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* MindMeld CLI -
|
|
3
|
+
* MindMeld CLI - Intelligent standards injection for AI coding sessions
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* mindmeld init # Initialize
|
|
7
|
-
* mindmeld
|
|
8
|
-
* mindmeld
|
|
6
|
+
* mindmeld init # Initialize project (requires authentication)
|
|
7
|
+
* mindmeld inject # Generate context-aware standards
|
|
8
|
+
* mindmeld harvest # Capture patterns from git history
|
|
9
|
+
* mindmeld logout # Clear stored authentication
|
|
9
10
|
*
|
|
10
|
-
* @equilateral_ai/mindmeld v3.
|
|
11
|
+
* @equilateral_ai/mindmeld v3.1.0
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
const fs = require('fs').promises;
|
|
15
|
+
const fsSync = require('fs');
|
|
14
16
|
const path = require('path');
|
|
17
|
+
const http = require('http');
|
|
18
|
+
const https = require('https');
|
|
19
|
+
const crypto = require('crypto');
|
|
15
20
|
const { exec } = require('child_process');
|
|
16
21
|
const { promisify } = require('util');
|
|
22
|
+
const os = require('os');
|
|
17
23
|
|
|
18
24
|
const execAsync = promisify(exec);
|
|
19
25
|
|
|
20
|
-
//
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Configuration
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
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
|
+
// API settings (production)
|
|
38
|
+
api: {
|
|
39
|
+
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 {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Save authentication to disk
|
|
65
|
+
*/
|
|
66
|
+
async function saveAuth(auth) {
|
|
67
|
+
const dir = path.dirname(CONFIG.authFile);
|
|
68
|
+
await fs.mkdir(dir, { recursive: true });
|
|
69
|
+
await fs.writeFile(CONFIG.authFile, JSON.stringify(auth, null, 2));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Clear stored authentication
|
|
74
|
+
*/
|
|
75
|
+
async function clearAuth() {
|
|
76
|
+
try {
|
|
77
|
+
await fs.unlink(CONFIG.authFile);
|
|
78
|
+
} catch {
|
|
79
|
+
// File doesn't exist, that's fine
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Decode JWT payload (without verification - Cognito already verified)
|
|
85
|
+
*/
|
|
86
|
+
function decodeJwt(token) {
|
|
87
|
+
const parts = token.split('.');
|
|
88
|
+
if (parts.length !== 3) throw new Error('Invalid JWT');
|
|
89
|
+
const payload = Buffer.from(parts[1], 'base64url').toString('utf-8');
|
|
90
|
+
return JSON.parse(payload);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if token is expired (with 5 min buffer)
|
|
95
|
+
*/
|
|
96
|
+
function isTokenExpired(expiresAt) {
|
|
97
|
+
return Date.now() > (expiresAt - 5 * 60 * 1000);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Refresh tokens using refresh token
|
|
102
|
+
*/
|
|
103
|
+
async function refreshTokens(refreshToken) {
|
|
104
|
+
const tokenUrl = `https://${CONFIG.cognito.domain}/oauth2/token`;
|
|
105
|
+
|
|
106
|
+
const params = new URLSearchParams({
|
|
107
|
+
grant_type: 'refresh_token',
|
|
108
|
+
client_id: CONFIG.cognito.clientId,
|
|
109
|
+
refresh_token: refreshToken
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
const req = https.request(tokenUrl, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: {
|
|
116
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
117
|
+
}
|
|
118
|
+
}, (res) => {
|
|
119
|
+
let data = '';
|
|
120
|
+
res.on('data', chunk => data += chunk);
|
|
121
|
+
res.on('end', () => {
|
|
122
|
+
if (res.statusCode !== 200) {
|
|
123
|
+
reject(new Error('Token refresh failed'));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const tokens = JSON.parse(data);
|
|
128
|
+
const decoded = decodeJwt(tokens.id_token);
|
|
129
|
+
resolve({
|
|
130
|
+
id_token: tokens.id_token,
|
|
131
|
+
access_token: tokens.access_token,
|
|
132
|
+
refresh_token: refreshToken, // Cognito doesn't return new refresh token
|
|
133
|
+
expires_at: Date.now() + (tokens.expires_in * 1000),
|
|
134
|
+
email: decoded.email
|
|
135
|
+
});
|
|
136
|
+
} catch (e) {
|
|
137
|
+
reject(e);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
req.on('error', reject);
|
|
143
|
+
req.write(params.toString());
|
|
144
|
+
req.end();
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Find an available port from the allowed list
|
|
150
|
+
*/
|
|
151
|
+
async function findAvailablePort() {
|
|
152
|
+
for (const port of CONFIG.callbackPorts) {
|
|
153
|
+
const available = await new Promise((resolve) => {
|
|
154
|
+
const server = http.createServer();
|
|
155
|
+
server.listen(port, '127.0.0.1');
|
|
156
|
+
server.on('listening', () => {
|
|
157
|
+
server.close();
|
|
158
|
+
resolve(true);
|
|
159
|
+
});
|
|
160
|
+
server.on('error', () => {
|
|
161
|
+
resolve(false);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
if (available) return port;
|
|
165
|
+
}
|
|
166
|
+
throw new Error('No available ports for OAuth callback');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Start local server to receive OAuth callback
|
|
171
|
+
*/
|
|
172
|
+
function startCallbackServer(port, expectedState) {
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
const server = http.createServer((req, res) => {
|
|
175
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
176
|
+
|
|
177
|
+
if (url.pathname === '/callback') {
|
|
178
|
+
const code = url.searchParams.get('code');
|
|
179
|
+
const state = url.searchParams.get('state');
|
|
180
|
+
const error = url.searchParams.get('error');
|
|
181
|
+
|
|
182
|
+
if (error) {
|
|
183
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
184
|
+
res.end(`
|
|
185
|
+
<html><body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
186
|
+
<h1>Authentication Failed</h1>
|
|
187
|
+
<p>Error: ${error}</p>
|
|
188
|
+
<p>You can close this window.</p>
|
|
189
|
+
</body></html>
|
|
190
|
+
`);
|
|
191
|
+
server.close();
|
|
192
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (state !== expectedState) {
|
|
197
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
198
|
+
res.end(`
|
|
199
|
+
<html><body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
200
|
+
<h1>Authentication Failed</h1>
|
|
201
|
+
<p>Invalid state parameter (possible CSRF attack)</p>
|
|
202
|
+
<p>You can close this window.</p>
|
|
203
|
+
</body></html>
|
|
204
|
+
`);
|
|
205
|
+
server.close();
|
|
206
|
+
reject(new Error('Invalid state parameter'));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
211
|
+
res.end(`
|
|
212
|
+
<html><body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
213
|
+
<h1>Authentication Successful!</h1>
|
|
214
|
+
<p>You can close this window and return to your terminal.</p>
|
|
215
|
+
</body></html>
|
|
216
|
+
`);
|
|
217
|
+
server.close();
|
|
218
|
+
resolve(code);
|
|
219
|
+
} else {
|
|
220
|
+
res.writeHead(404);
|
|
221
|
+
res.end('Not found');
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
server.listen(port, '127.0.0.1');
|
|
226
|
+
|
|
227
|
+
// Timeout after 5 minutes
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
server.close();
|
|
230
|
+
reject(new Error('Authentication timed out'));
|
|
231
|
+
}, 5 * 60 * 1000);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Exchange authorization code for tokens
|
|
237
|
+
*/
|
|
238
|
+
async function exchangeCodeForTokens(code, codeVerifier, port) {
|
|
239
|
+
const tokenUrl = `https://${CONFIG.cognito.domain}/oauth2/token`;
|
|
240
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
241
|
+
|
|
242
|
+
const params = new URLSearchParams({
|
|
243
|
+
grant_type: 'authorization_code',
|
|
244
|
+
client_id: CONFIG.cognito.clientId,
|
|
245
|
+
code: code,
|
|
246
|
+
redirect_uri: redirectUri,
|
|
247
|
+
code_verifier: codeVerifier
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return new Promise((resolve, reject) => {
|
|
251
|
+
const req = https.request(tokenUrl, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: {
|
|
254
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
255
|
+
}
|
|
256
|
+
}, (res) => {
|
|
257
|
+
let data = '';
|
|
258
|
+
res.on('data', chunk => data += chunk);
|
|
259
|
+
res.on('end', () => {
|
|
260
|
+
if (res.statusCode !== 200) {
|
|
261
|
+
reject(new Error(`Token exchange failed: ${data}`));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
const tokens = JSON.parse(data);
|
|
266
|
+
const decoded = decodeJwt(tokens.id_token);
|
|
267
|
+
resolve({
|
|
268
|
+
id_token: tokens.id_token,
|
|
269
|
+
access_token: tokens.access_token,
|
|
270
|
+
refresh_token: tokens.refresh_token,
|
|
271
|
+
expires_at: Date.now() + (tokens.expires_in * 1000),
|
|
272
|
+
email: decoded.email
|
|
273
|
+
});
|
|
274
|
+
} catch (e) {
|
|
275
|
+
reject(e);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
req.on('error', reject);
|
|
281
|
+
req.write(params.toString());
|
|
282
|
+
req.end();
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Open browser for authentication
|
|
288
|
+
*/
|
|
289
|
+
async function openBrowser(url) {
|
|
290
|
+
const platform = process.platform;
|
|
291
|
+
let command;
|
|
292
|
+
|
|
293
|
+
if (platform === 'darwin') {
|
|
294
|
+
command = `open "${url}"`;
|
|
295
|
+
} else if (platform === 'win32') {
|
|
296
|
+
command = `start "" "${url}"`;
|
|
297
|
+
} else {
|
|
298
|
+
command = `xdg-open "${url}"`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
await execAsync(command);
|
|
303
|
+
return true;
|
|
304
|
+
} catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Full browser authentication flow (PKCE)
|
|
311
|
+
*/
|
|
312
|
+
async function browserAuth() {
|
|
313
|
+
// Generate PKCE challenge
|
|
314
|
+
const codeVerifier = crypto.randomBytes(32).toString('base64url');
|
|
315
|
+
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
|
|
316
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
317
|
+
|
|
318
|
+
// Find available port
|
|
319
|
+
const port = await findAvailablePort();
|
|
320
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
321
|
+
|
|
322
|
+
// Build Cognito authorization URL
|
|
323
|
+
const authUrl = `https://${CONFIG.cognito.domain}/login?` + new URLSearchParams({
|
|
324
|
+
client_id: CONFIG.cognito.clientId,
|
|
325
|
+
response_type: 'code',
|
|
326
|
+
scope: 'openid email profile',
|
|
327
|
+
redirect_uri: redirectUri,
|
|
328
|
+
state: state,
|
|
329
|
+
code_challenge: codeChallenge,
|
|
330
|
+
code_challenge_method: 'S256'
|
|
331
|
+
}).toString();
|
|
332
|
+
|
|
333
|
+
// Start callback server
|
|
334
|
+
const codePromise = startCallbackServer(port, state);
|
|
335
|
+
|
|
336
|
+
// Open browser
|
|
337
|
+
console.log('\n🔐 Opening browser for authentication...');
|
|
338
|
+
console.log(` Callback server listening on port ${port}`);
|
|
339
|
+
|
|
340
|
+
const opened = await openBrowser(authUrl);
|
|
341
|
+
|
|
342
|
+
if (!opened) {
|
|
343
|
+
console.log('\n Could not open browser automatically.');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log('\n If login page doesn\'t appear, paste this URL in your browser:\n');
|
|
347
|
+
console.log(` ${authUrl}\n`);
|
|
348
|
+
|
|
349
|
+
console.log(' Waiting for authentication (5 minute timeout)...\n');
|
|
350
|
+
|
|
351
|
+
// Wait for callback
|
|
352
|
+
const code = await codePromise;
|
|
353
|
+
|
|
354
|
+
// Exchange code for tokens
|
|
355
|
+
const tokens = await exchangeCodeForTokens(code, codeVerifier, port);
|
|
356
|
+
await saveAuth(tokens);
|
|
357
|
+
|
|
358
|
+
return tokens;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Ensure user is authenticated (main entry point)
|
|
363
|
+
*/
|
|
364
|
+
async function ensureAuth() {
|
|
365
|
+
let auth = loadAuth();
|
|
366
|
+
|
|
367
|
+
// Check if we have valid tokens
|
|
368
|
+
if (auth?.id_token && !isTokenExpired(auth.expires_at)) {
|
|
369
|
+
return auth;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Try silent refresh
|
|
373
|
+
if (auth?.refresh_token) {
|
|
374
|
+
try {
|
|
375
|
+
console.log('🔄 Refreshing authentication...');
|
|
376
|
+
const newAuth = await refreshTokens(auth.refresh_token);
|
|
377
|
+
await saveAuth(newAuth);
|
|
378
|
+
return newAuth;
|
|
379
|
+
} catch (e) {
|
|
380
|
+
console.log(' Refresh failed, need to re-authenticate.\n');
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Full browser auth
|
|
385
|
+
return await browserAuth();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ============================================================================
|
|
389
|
+
// API Module
|
|
390
|
+
// ============================================================================
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Make authenticated API call
|
|
394
|
+
*/
|
|
395
|
+
function callApi(method, endpoint, token, body = null) {
|
|
396
|
+
// Construct full URL - endpoint is relative, baseUrl may include path like /dev
|
|
397
|
+
const baseUrl = new URL(CONFIG.api.baseUrl);
|
|
398
|
+
const fullPath = baseUrl.pathname.replace(/\/$/, '') + endpoint;
|
|
399
|
+
|
|
400
|
+
return new Promise((resolve, reject) => {
|
|
401
|
+
const options = {
|
|
402
|
+
method,
|
|
403
|
+
hostname: baseUrl.hostname,
|
|
404
|
+
path: fullPath,
|
|
405
|
+
headers: {
|
|
406
|
+
'Authorization': `Bearer ${token}`,
|
|
407
|
+
'Content-Type': 'application/json'
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const req = https.request(options, (res) => {
|
|
412
|
+
let data = '';
|
|
413
|
+
res.on('data', chunk => data += chunk);
|
|
414
|
+
res.on('end', () => {
|
|
415
|
+
try {
|
|
416
|
+
const parsed = JSON.parse(data);
|
|
417
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
418
|
+
resolve({ success: true, data: parsed });
|
|
419
|
+
} else {
|
|
420
|
+
resolve({ success: false, error: parsed.message || parsed.error || 'API error', status: res.statusCode });
|
|
421
|
+
}
|
|
422
|
+
} catch {
|
|
423
|
+
resolve({ success: false, error: data, status: res.statusCode });
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
req.on('error', (e) => {
|
|
429
|
+
resolve({ success: false, error: e.message });
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
if (body) {
|
|
433
|
+
req.write(JSON.stringify(body));
|
|
434
|
+
}
|
|
435
|
+
req.end();
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Poll for subscription activation after Stripe checkout
|
|
441
|
+
*/
|
|
442
|
+
async function pollForSubscription(token, maxAttempts = 60) {
|
|
443
|
+
console.log('\n⏳ Waiting for subscription activation...');
|
|
444
|
+
console.log(' (Complete the checkout in your browser)\n');
|
|
445
|
+
|
|
446
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
447
|
+
await new Promise(r => setTimeout(r, 3000)); // Wait 3 seconds
|
|
448
|
+
|
|
449
|
+
const response = await callApi('GET', '/api/users/me', token);
|
|
450
|
+
if (response.success && response.data?.Records?.[0]) {
|
|
451
|
+
const user = response.data.Records[0];
|
|
452
|
+
const tier = user.subscription?.tier || user.client?.subscription_tier || 'free';
|
|
453
|
+
if (tier !== 'free') {
|
|
454
|
+
console.log(`✅ Subscription activated: ${tier}\n`);
|
|
455
|
+
return tier;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
process.stdout.write('.');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
console.log('\n⚠️ Subscription not detected. You can continue setup and it will sync later.\n');
|
|
462
|
+
return 'free';
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ============================================================================
|
|
466
|
+
// CLI Arguments
|
|
467
|
+
// ============================================================================
|
|
468
|
+
|
|
21
469
|
function parseArgs(args) {
|
|
22
470
|
const parsed = {
|
|
23
471
|
command: null,
|
|
24
472
|
projectPath: null,
|
|
25
|
-
team: false,
|
|
26
473
|
format: 'raw',
|
|
27
474
|
since: '7d',
|
|
28
475
|
commits: 10,
|
|
@@ -32,19 +479,17 @@ function parseArgs(args) {
|
|
|
32
479
|
|
|
33
480
|
for (let i = 0; i < args.length; i++) {
|
|
34
481
|
const arg = args[i];
|
|
35
|
-
if (arg === '--
|
|
36
|
-
else if (arg === '--dry-run') parsed.dryRun = true;
|
|
482
|
+
if (arg === '--dry-run') parsed.dryRun = true;
|
|
37
483
|
else if (arg === '--help' || arg === '-h') parsed.help = true;
|
|
38
484
|
else if (arg === '--format' && args[i + 1]) { parsed.format = args[++i]; }
|
|
39
485
|
else if (arg === '--path' && args[i + 1]) { parsed.projectPath = args[++i]; }
|
|
40
486
|
else if (arg === '--since' && args[i + 1]) { parsed.since = args[++i]; }
|
|
41
487
|
else if (arg === '--commits' && args[i + 1]) { parsed.commits = parseInt(args[++i]); }
|
|
42
|
-
else if (['init', 'inject', 'harvest'].includes(arg)) parsed.command = arg;
|
|
488
|
+
else if (['init', 'inject', 'harvest', 'logout', 'login', 'status'].includes(arg)) parsed.command = arg;
|
|
43
489
|
else if (!arg.startsWith('-') && !parsed.command) parsed.command = arg;
|
|
44
490
|
else if (!arg.startsWith('-') && !parsed.projectPath) parsed.projectPath = arg;
|
|
45
491
|
}
|
|
46
492
|
|
|
47
|
-
// Default command is 'init'
|
|
48
493
|
if (!parsed.command) parsed.command = 'init';
|
|
49
494
|
|
|
50
495
|
return parsed;
|
|
@@ -58,24 +503,25 @@ Usage:
|
|
|
58
503
|
mindmeld <command> [options]
|
|
59
504
|
|
|
60
505
|
Commands:
|
|
61
|
-
init [path] Initialize project for MindMeld
|
|
506
|
+
init [path] Initialize project for MindMeld (requires login)
|
|
62
507
|
inject Generate context-aware standards for any AI tool
|
|
63
508
|
harvest Manually capture patterns from recent git history
|
|
509
|
+
login Authenticate with MindMeld
|
|
510
|
+
logout Clear stored authentication
|
|
511
|
+
status Show current authentication status
|
|
64
512
|
|
|
65
513
|
Options:
|
|
66
|
-
--team Enable team collaboration (init only)
|
|
67
514
|
--format <type> Output format: raw, cursorrules, windsurfrules, aider, claude
|
|
68
515
|
--path <dir> Project path (default: current directory)
|
|
69
516
|
--help, -h Show this help message
|
|
70
517
|
|
|
71
518
|
Examples:
|
|
72
|
-
mindmeld init
|
|
73
|
-
mindmeld inject --format cursorrules
|
|
74
|
-
mindmeld inject --format windsurfrules
|
|
75
|
-
mindmeld inject
|
|
76
|
-
mindmeld
|
|
77
|
-
mindmeld
|
|
78
|
-
mindmeld harvest # Capture patterns from git diff
|
|
519
|
+
mindmeld init # Initialize with authentication
|
|
520
|
+
mindmeld inject --format cursorrules # Update .cursorrules
|
|
521
|
+
mindmeld inject --format windsurfrules # Update .windsurfrules
|
|
522
|
+
mindmeld inject # Raw markdown to stdout
|
|
523
|
+
mindmeld harvest # Capture patterns from git diff
|
|
524
|
+
mindmeld status # Check auth status
|
|
79
525
|
|
|
80
526
|
Works with: Claude Code, Cursor, Windsurf, Codex CLI, Aider, Ollama, LM Studio
|
|
81
527
|
|
|
@@ -83,18 +529,68 @@ Learn more: https://mindmeld.dev
|
|
|
83
529
|
`);
|
|
84
530
|
}
|
|
85
531
|
|
|
86
|
-
|
|
532
|
+
// ============================================================================
|
|
533
|
+
// Init Command
|
|
534
|
+
// ============================================================================
|
|
535
|
+
|
|
536
|
+
async function initProject(projectPath) {
|
|
87
537
|
projectPath = projectPath || process.cwd();
|
|
88
538
|
|
|
89
|
-
console.log(
|
|
539
|
+
console.log('\n🎯 MindMeld CLI\n');
|
|
540
|
+
|
|
541
|
+
// 1. Authenticate
|
|
542
|
+
const auth = await ensureAuth();
|
|
543
|
+
console.log(`✅ Authenticated as ${auth.email}\n`);
|
|
544
|
+
|
|
545
|
+
// 2. Check subscription
|
|
546
|
+
const userResponse = await callApi('GET', '/api/users/me', auth.id_token);
|
|
547
|
+
if (!userResponse.success) {
|
|
548
|
+
throw new Error(`Failed to get user info: ${userResponse.error}`);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const user = userResponse.data.data?.Records?.[0];
|
|
552
|
+
if (!user) {
|
|
553
|
+
throw new Error('User not found. Please sign up at https://app.mindmeld.dev');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
let tier = user.subscription?.tier || user.client?.subscription_tier || 'free';
|
|
557
|
+
const clientId = user.client_id;
|
|
558
|
+
|
|
559
|
+
// 3. Handle free tier - prompt for upgrade
|
|
560
|
+
if (tier === 'free') {
|
|
561
|
+
console.log('📋 Current plan: Free\n');
|
|
562
|
+
console.log(' MindMeld requires a subscription for full features.');
|
|
563
|
+
console.log(' Free tier provides local-only pattern storage.\n');
|
|
564
|
+
|
|
565
|
+
const upgrade = await promptYesNo(' Would you like to upgrade now? (y/n): ');
|
|
566
|
+
|
|
567
|
+
if (upgrade) {
|
|
568
|
+
// Create checkout session
|
|
569
|
+
const checkoutResponse = await callApi('POST', '/api/stripe/subscription/create', auth.id_token, {
|
|
570
|
+
tier: 'team',
|
|
571
|
+
contributionMode: 'contributing'
|
|
572
|
+
});
|
|
90
573
|
|
|
91
|
-
|
|
92
|
-
|
|
574
|
+
if (checkoutResponse.success && checkoutResponse.data?.data?.url) {
|
|
575
|
+
console.log('\n🛒 Opening checkout...');
|
|
576
|
+
await openBrowser(checkoutResponse.data.data.url);
|
|
577
|
+
tier = await pollForSubscription(auth.id_token);
|
|
578
|
+
} else {
|
|
579
|
+
console.log(`\n⚠️ Could not create checkout: ${checkoutResponse.error}`);
|
|
580
|
+
console.log(' Visit https://app.mindmeld.dev to subscribe.\n');
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
console.log('\n Continuing with free tier (local-only mode).\n');
|
|
584
|
+
}
|
|
93
585
|
} else {
|
|
94
|
-
console.log(
|
|
586
|
+
console.log(`📋 Current plan: ${tier}\n`);
|
|
95
587
|
}
|
|
96
588
|
|
|
97
|
-
//
|
|
589
|
+
// 4. Get project info
|
|
590
|
+
const projectName = path.basename(projectPath);
|
|
591
|
+
const projectId = projectName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
592
|
+
|
|
593
|
+
// 5. Check if already initialized
|
|
98
594
|
const mindmeldDir = path.join(projectPath, '.mindmeld');
|
|
99
595
|
const configPath = path.join(mindmeldDir, 'config.json');
|
|
100
596
|
|
|
@@ -103,7 +599,7 @@ async function initProject(projectPath, options = {}) {
|
|
|
103
599
|
console.log('⚠️ Project already initialized!');
|
|
104
600
|
console.log(` Config exists at: ${configPath}\n`);
|
|
105
601
|
|
|
106
|
-
const answer = await promptYesNo('Reinitialize? (y/n): ');
|
|
602
|
+
const answer = await promptYesNo(' Reinitialize? (y/n): ');
|
|
107
603
|
if (!answer) {
|
|
108
604
|
console.log('Aborted.');
|
|
109
605
|
process.exit(0);
|
|
@@ -112,13 +608,32 @@ async function initProject(projectPath, options = {}) {
|
|
|
112
608
|
// Not initialized, continue
|
|
113
609
|
}
|
|
114
610
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
611
|
+
// 6. Create project on backend (if subscribed)
|
|
612
|
+
let backendProjectId = null;
|
|
613
|
+
if (tier !== 'free') {
|
|
614
|
+
console.log('📡 Registering project with MindMeld...');
|
|
615
|
+
// Company_ID is client_id + '_main' for personal workspaces
|
|
616
|
+
const companyId = `${clientId}_main`;
|
|
617
|
+
const projectResponse = await callApi('POST', '/api/projects', auth.id_token, {
|
|
618
|
+
Company_ID: companyId,
|
|
619
|
+
project_name: projectName
|
|
620
|
+
});
|
|
118
621
|
|
|
119
|
-
|
|
120
|
-
|
|
622
|
+
if (projectResponse.success) {
|
|
623
|
+
backendProjectId = projectResponse.data?.data?.Records?.[0]?.project_id || projectId;
|
|
624
|
+
console.log(` Project registered: ${backendProjectId}\n`);
|
|
625
|
+
} else if (projectResponse.status === 409) {
|
|
626
|
+
// Project already exists, that's fine
|
|
627
|
+
backendProjectId = projectId;
|
|
628
|
+
console.log(` Project already registered: ${projectId}\n`);
|
|
629
|
+
} else {
|
|
630
|
+
console.log(` Warning: Could not register project: ${projectResponse.error}`);
|
|
631
|
+
console.log(' Continuing with local-only setup.\n');
|
|
632
|
+
}
|
|
633
|
+
}
|
|
121
634
|
|
|
635
|
+
// 7. Discover collaborators from git
|
|
636
|
+
let collaborators = [];
|
|
122
637
|
try {
|
|
123
638
|
const { stdout } = await execAsync(
|
|
124
639
|
'git log --format="%an|%ae" | sort | uniq -c | sort -rn | head -10',
|
|
@@ -144,95 +659,86 @@ async function initProject(projectPath, options = {}) {
|
|
|
144
659
|
}
|
|
145
660
|
|
|
146
661
|
if (collaborators.length > 0) {
|
|
147
|
-
console.log('
|
|
662
|
+
console.log('👥 Discovered collaborators from git history:\n');
|
|
148
663
|
collaborators.forEach((c, i) => {
|
|
149
664
|
console.log(` ${i + 1}. ${c.name} <${c.email}> (${c.commits} commits)`);
|
|
150
665
|
});
|
|
151
666
|
console.log('');
|
|
152
667
|
}
|
|
153
|
-
} catch
|
|
668
|
+
} catch {
|
|
154
669
|
console.log('ℹ️ No git history found (not a git repo or no commits)\n');
|
|
155
670
|
}
|
|
156
671
|
|
|
157
|
-
//
|
|
672
|
+
// 8. Create .mindmeld directory and config
|
|
158
673
|
await fs.mkdir(mindmeldDir, { recursive: true });
|
|
159
674
|
|
|
160
|
-
// 5. Create config.json
|
|
161
675
|
const config = {
|
|
162
|
-
projectId,
|
|
676
|
+
projectId: backendProjectId || projectId,
|
|
163
677
|
projectName,
|
|
164
|
-
|
|
678
|
+
userEmail: auth.email,
|
|
679
|
+
clientId,
|
|
680
|
+
subscriptionTier: tier,
|
|
165
681
|
collaborators,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
externalUsersAllowed: false,
|
|
169
|
-
mindmeldVersion: '3.0.0'
|
|
682
|
+
created: new Date().toISOString(),
|
|
683
|
+
mindmeldVersion: '3.1.0'
|
|
170
684
|
};
|
|
171
685
|
|
|
172
686
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
173
687
|
|
|
174
688
|
console.log('✅ MindMeld initialized!\n');
|
|
175
689
|
console.log(` Config: ${configPath}`);
|
|
176
|
-
console.log(` Project ID: ${projectId}`);
|
|
177
|
-
console.log(`
|
|
178
|
-
console.log(`
|
|
690
|
+
console.log(` Project ID: ${config.projectId}`);
|
|
691
|
+
console.log(` User: ${auth.email}`);
|
|
692
|
+
console.log(` Plan: ${tier}`);
|
|
179
693
|
console.log('');
|
|
180
694
|
|
|
181
|
-
//
|
|
695
|
+
// 9. Check for community standards
|
|
182
696
|
const standardsDir = path.join(projectPath, '.equilateral-standards');
|
|
183
|
-
|
|
184
697
|
try {
|
|
185
698
|
await fs.access(standardsDir);
|
|
186
699
|
console.log('ℹ️ Community standards (.equilateral-standards) available');
|
|
187
700
|
} catch {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
701
|
+
if (tier !== 'free') {
|
|
702
|
+
try {
|
|
703
|
+
await execAsync(
|
|
704
|
+
'git clone https://github.com/Equilateral-AI/EquilateralAgents-Community-Standards.git .equilateral-standards',
|
|
705
|
+
{ cwd: projectPath }
|
|
706
|
+
);
|
|
707
|
+
console.log('✅ Cloned community standards repo');
|
|
708
|
+
} catch {
|
|
709
|
+
console.log('ℹ️ Community standards not available (clone from:');
|
|
710
|
+
console.log(' https://github.com/Equilateral-AI/EquilateralAgents-Community-Standards)');
|
|
711
|
+
}
|
|
199
712
|
}
|
|
200
713
|
}
|
|
201
714
|
|
|
202
|
-
//
|
|
203
|
-
await bootstrapFromHistory(projectPath
|
|
715
|
+
// 10. Bootstrap from git history
|
|
716
|
+
await bootstrapFromHistory(projectPath);
|
|
204
717
|
|
|
205
|
-
//
|
|
718
|
+
// 11. Configure Claude Code hooks
|
|
206
719
|
await configureClaudeHooks(projectPath);
|
|
207
720
|
|
|
208
|
-
//
|
|
209
|
-
if (options.team) {
|
|
210
|
-
console.log('\n📡 Team mode enabled.');
|
|
211
|
-
console.log(' Sign in at https://app.mindmeld.dev to connect this project.');
|
|
212
|
-
console.log(' Your coding sessions will contribute to team-wide standards.\n');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// 10. Summary
|
|
721
|
+
// 12. Summary
|
|
216
722
|
console.log('\n🚀 Next steps:');
|
|
217
723
|
console.log(' 1. Start a Claude Code session in this project');
|
|
218
724
|
console.log(' 2. MindMeld hooks will inject relevant standards automatically');
|
|
219
725
|
console.log(' 3. Patterns from your sessions will be harvested and validated');
|
|
220
|
-
if (
|
|
221
|
-
console.log(' 4.
|
|
726
|
+
if (tier !== 'free') {
|
|
727
|
+
console.log(' 4. Visit https://app.mindmeld.dev to manage team standards');
|
|
222
728
|
}
|
|
223
729
|
console.log('');
|
|
224
730
|
}
|
|
225
731
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
732
|
+
// ============================================================================
|
|
733
|
+
// Bootstrap & Hooks (unchanged)
|
|
734
|
+
// ============================================================================
|
|
735
|
+
|
|
736
|
+
async function bootstrapFromHistory(projectPath) {
|
|
230
737
|
console.log('\n📊 Bootstrapping from git history...\n');
|
|
231
738
|
|
|
232
739
|
try {
|
|
233
740
|
const { getGitHistory, detectPatterns, savePatterns, promotePatterns, harvestPlans, promoteDecisions } = require('./harvest');
|
|
234
741
|
|
|
235
|
-
// Get extended git history
|
|
236
742
|
const gitHistory = await getGitHistory(projectPath, { since: '90d', commits: 50 });
|
|
237
743
|
const patterns = detectPatterns(gitHistory);
|
|
238
744
|
|
|
@@ -242,10 +748,7 @@ async function bootstrapFromHistory(projectPath, options = {}) {
|
|
|
242
748
|
return;
|
|
243
749
|
}
|
|
244
750
|
|
|
245
|
-
// Save raw patterns
|
|
246
751
|
await savePatterns(projectPath, patterns);
|
|
247
|
-
|
|
248
|
-
// Promote high-confidence patterns to provisional standards
|
|
249
752
|
const promoted = await promotePatterns(projectPath, patterns, { threshold: 0.5 });
|
|
250
753
|
|
|
251
754
|
console.log(` Detected ${patterns.length} pattern(s) from git history`);
|
|
@@ -256,7 +759,6 @@ async function bootstrapFromHistory(projectPath, options = {}) {
|
|
|
256
759
|
}
|
|
257
760
|
}
|
|
258
761
|
|
|
259
|
-
// Harvest decisions from Claude Code plan files
|
|
260
762
|
const planDecisions = await harvestPlans(projectPath);
|
|
261
763
|
if (planDecisions.length > 0) {
|
|
262
764
|
const promotedDecisions = await promoteDecisions(projectPath, planDecisions);
|
|
@@ -279,20 +781,14 @@ async function bootstrapFromHistory(projectPath, options = {}) {
|
|
|
279
781
|
}
|
|
280
782
|
}
|
|
281
783
|
|
|
282
|
-
/**
|
|
283
|
-
* Configure Claude Code hooks in .claude/settings.json
|
|
284
|
-
* This wires up MindMeld's session-start and pre-compact hooks
|
|
285
|
-
*/
|
|
286
784
|
async function configureClaudeHooks(projectPath) {
|
|
287
785
|
const claudeDir = path.join(projectPath, '.claude');
|
|
288
786
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
289
787
|
|
|
290
|
-
// Resolve hook paths from installed package location
|
|
291
788
|
const packageRoot = path.resolve(__dirname, '..');
|
|
292
789
|
const sessionStartHook = path.join(packageRoot, 'hooks', 'session-start.js');
|
|
293
790
|
const preCompactHook = path.join(packageRoot, 'hooks', 'pre-compact.js');
|
|
294
791
|
|
|
295
|
-
// Verify hooks exist
|
|
296
792
|
try {
|
|
297
793
|
await fs.access(sessionStartHook);
|
|
298
794
|
await fs.access(preCompactHook);
|
|
@@ -302,50 +798,37 @@ async function configureClaudeHooks(projectPath) {
|
|
|
302
798
|
return;
|
|
303
799
|
}
|
|
304
800
|
|
|
305
|
-
// MindMeld hook definitions
|
|
306
801
|
const mindmeldHooks = {
|
|
307
|
-
SessionStart: [
|
|
308
|
-
{
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
{
|
|
322
|
-
type: 'command',
|
|
323
|
-
command: `node "${preCompactHook}"`,
|
|
324
|
-
timeout: 30
|
|
325
|
-
}
|
|
326
|
-
]
|
|
327
|
-
}
|
|
328
|
-
]
|
|
802
|
+
SessionStart: [{
|
|
803
|
+
hooks: [{
|
|
804
|
+
type: 'command',
|
|
805
|
+
command: `node "${sessionStartHook}"`,
|
|
806
|
+
timeout: 5
|
|
807
|
+
}]
|
|
808
|
+
}],
|
|
809
|
+
PreCompact: [{
|
|
810
|
+
hooks: [{
|
|
811
|
+
type: 'command',
|
|
812
|
+
command: `node "${preCompactHook}"`,
|
|
813
|
+
timeout: 30
|
|
814
|
+
}]
|
|
815
|
+
}]
|
|
329
816
|
};
|
|
330
817
|
|
|
331
|
-
// Create .claude directory
|
|
332
818
|
await fs.mkdir(claudeDir, { recursive: true });
|
|
333
819
|
|
|
334
|
-
// Load existing settings if present
|
|
335
820
|
let settings = {};
|
|
336
821
|
try {
|
|
337
822
|
const existing = await fs.readFile(settingsPath, 'utf-8');
|
|
338
823
|
settings = JSON.parse(existing);
|
|
339
824
|
} catch {
|
|
340
|
-
// No existing settings
|
|
825
|
+
// No existing settings
|
|
341
826
|
}
|
|
342
827
|
|
|
343
|
-
// Merge hooks (preserve existing non-MindMeld hooks)
|
|
344
828
|
if (!settings.hooks) {
|
|
345
829
|
settings.hooks = {};
|
|
346
830
|
}
|
|
347
831
|
|
|
348
|
-
// Check if MindMeld hooks already configured
|
|
349
832
|
const hasSessionStart = (settings.hooks.SessionStart || []).some(h =>
|
|
350
833
|
h.hooks?.some(hk => hk.command?.includes('mindmeld') || hk.command?.includes('session-start'))
|
|
351
834
|
);
|
|
@@ -358,7 +841,6 @@ async function configureClaudeHooks(projectPath) {
|
|
|
358
841
|
return;
|
|
359
842
|
}
|
|
360
843
|
|
|
361
|
-
// Add MindMeld hooks (append to existing, don't replace)
|
|
362
844
|
if (!hasSessionStart) {
|
|
363
845
|
settings.hooks.SessionStart = [
|
|
364
846
|
...(settings.hooks.SessionStart || []),
|
|
@@ -379,6 +861,39 @@ async function configureClaudeHooks(projectPath) {
|
|
|
379
861
|
console.log(` PreCompact: ${preCompactHook}`);
|
|
380
862
|
}
|
|
381
863
|
|
|
864
|
+
// ============================================================================
|
|
865
|
+
// Utility Commands
|
|
866
|
+
// ============================================================================
|
|
867
|
+
|
|
868
|
+
async function showStatus() {
|
|
869
|
+
const auth = loadAuth();
|
|
870
|
+
|
|
871
|
+
console.log('\n🎯 MindMeld Status\n');
|
|
872
|
+
|
|
873
|
+
if (!auth) {
|
|
874
|
+
console.log(' Not authenticated.');
|
|
875
|
+
console.log(' Run "mindmeld login" to authenticate.\n');
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
console.log(` Email: ${auth.email}`);
|
|
880
|
+
console.log(` Token expires: ${new Date(auth.expires_at).toLocaleString()}`);
|
|
881
|
+
console.log(` Token status: ${isTokenExpired(auth.expires_at) ? 'Expired' : 'Valid'}`);
|
|
882
|
+
console.log(` Refresh token: ${auth.refresh_token ? 'Present' : 'Missing'}`);
|
|
883
|
+
console.log('');
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
async function logout() {
|
|
887
|
+
await clearAuth();
|
|
888
|
+
console.log('\n✅ Logged out successfully.\n');
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
async function login() {
|
|
892
|
+
console.log('\n🎯 MindMeld Login\n');
|
|
893
|
+
const auth = await browserAuth();
|
|
894
|
+
console.log(`\n✅ Logged in as ${auth.email}\n`);
|
|
895
|
+
}
|
|
896
|
+
|
|
382
897
|
async function promptYesNo(question) {
|
|
383
898
|
process.stdout.write(question);
|
|
384
899
|
|
|
@@ -390,7 +905,10 @@ async function promptYesNo(question) {
|
|
|
390
905
|
});
|
|
391
906
|
}
|
|
392
907
|
|
|
908
|
+
// ============================================================================
|
|
393
909
|
// Main
|
|
910
|
+
// ============================================================================
|
|
911
|
+
|
|
394
912
|
const args = parseArgs(process.argv.slice(2));
|
|
395
913
|
|
|
396
914
|
if (args.help && !args.command) {
|
|
@@ -400,7 +918,7 @@ if (args.help && !args.command) {
|
|
|
400
918
|
|
|
401
919
|
if (args.command === 'init') {
|
|
402
920
|
if (args.help) { showHelp(); process.exit(0); }
|
|
403
|
-
initProject(args.projectPath
|
|
921
|
+
initProject(args.projectPath)
|
|
404
922
|
.then(() => process.exit(0))
|
|
405
923
|
.catch(error => {
|
|
406
924
|
console.error('\n❌ Error:', error.message);
|
|
@@ -408,10 +926,7 @@ if (args.command === 'init') {
|
|
|
408
926
|
});
|
|
409
927
|
} else if (args.command === 'inject') {
|
|
410
928
|
const { inject, showInjectHelp } = require('./inject');
|
|
411
|
-
if (args.help) {
|
|
412
|
-
showInjectHelp();
|
|
413
|
-
process.exit(0);
|
|
414
|
-
}
|
|
929
|
+
if (args.help) { showInjectHelp(); process.exit(0); }
|
|
415
930
|
inject({ format: args.format, path: args.projectPath })
|
|
416
931
|
.then(() => process.exit(0))
|
|
417
932
|
.catch(error => {
|
|
@@ -420,16 +935,34 @@ if (args.command === 'init') {
|
|
|
420
935
|
});
|
|
421
936
|
} else if (args.command === 'harvest') {
|
|
422
937
|
const { harvest, showHarvestHelp } = require('./harvest');
|
|
423
|
-
if (args.help) {
|
|
424
|
-
showHarvestHelp();
|
|
425
|
-
process.exit(0);
|
|
426
|
-
}
|
|
938
|
+
if (args.help) { showHarvestHelp(); process.exit(0); }
|
|
427
939
|
harvest({ path: args.projectPath, since: args.since, commits: args.commits, dryRun: args.dryRun })
|
|
428
940
|
.then(() => process.exit(0))
|
|
429
941
|
.catch(error => {
|
|
430
942
|
console.error('\n❌ Error:', error.message);
|
|
431
943
|
process.exit(1);
|
|
432
944
|
});
|
|
945
|
+
} else if (args.command === 'logout') {
|
|
946
|
+
logout()
|
|
947
|
+
.then(() => process.exit(0))
|
|
948
|
+
.catch(error => {
|
|
949
|
+
console.error('\n❌ Error:', error.message);
|
|
950
|
+
process.exit(1);
|
|
951
|
+
});
|
|
952
|
+
} else if (args.command === 'login') {
|
|
953
|
+
login()
|
|
954
|
+
.then(() => process.exit(0))
|
|
955
|
+
.catch(error => {
|
|
956
|
+
console.error('\n❌ Error:', error.message);
|
|
957
|
+
process.exit(1);
|
|
958
|
+
});
|
|
959
|
+
} else if (args.command === 'status') {
|
|
960
|
+
showStatus()
|
|
961
|
+
.then(() => process.exit(0))
|
|
962
|
+
.catch(error => {
|
|
963
|
+
console.error('\n❌ Error:', error.message);
|
|
964
|
+
process.exit(1);
|
|
965
|
+
});
|
|
433
966
|
} else {
|
|
434
967
|
console.error(`Unknown command: ${args.command}`);
|
|
435
968
|
console.error('Run "mindmeld --help" for usage.');
|