@equilateral_ai/mindmeld 3.0.0 → 3.1.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/package.json +1 -1
- package/scripts/init-project.js +655 -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 (dev - production API is currently broken)
|
|
32
|
+
cognito: {
|
|
33
|
+
domain: 'mindmeld-users.auth.us-east-2.amazoncognito.com',
|
|
34
|
+
clientId: '6uif970sisfpvk5r6vg17uvf8r',
|
|
35
|
+
region: 'us-east-2'
|
|
36
|
+
},
|
|
37
|
+
// API settings (using direct API Gateway URL - custom domain has SSL issues)
|
|
38
|
+
api: {
|
|
39
|
+
baseUrl: 'https://u16nv4eqib.execute-api.us-east-2.amazonaws.com/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,30 @@ 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
|
+
const projectResponse = await callApi('POST', '/api/projects', auth.id_token, {
|
|
616
|
+
Company_ID: clientId,
|
|
617
|
+
project_name: projectName
|
|
618
|
+
});
|
|
118
619
|
|
|
119
|
-
|
|
120
|
-
|
|
620
|
+
if (projectResponse.success) {
|
|
621
|
+
backendProjectId = projectResponse.data?.data?.Records?.[0]?.project_id || projectId;
|
|
622
|
+
console.log(` Project registered: ${backendProjectId}\n`);
|
|
623
|
+
} else if (projectResponse.status === 409) {
|
|
624
|
+
// Project already exists, that's fine
|
|
625
|
+
backendProjectId = projectId;
|
|
626
|
+
console.log(` Project already registered: ${projectId}\n`);
|
|
627
|
+
} else {
|
|
628
|
+
console.log(` Warning: Could not register project: ${projectResponse.error}`);
|
|
629
|
+
console.log(' Continuing with local-only setup.\n');
|
|
630
|
+
}
|
|
631
|
+
}
|
|
121
632
|
|
|
633
|
+
// 7. Discover collaborators from git
|
|
634
|
+
let collaborators = [];
|
|
122
635
|
try {
|
|
123
636
|
const { stdout } = await execAsync(
|
|
124
637
|
'git log --format="%an|%ae" | sort | uniq -c | sort -rn | head -10',
|
|
@@ -144,95 +657,86 @@ async function initProject(projectPath, options = {}) {
|
|
|
144
657
|
}
|
|
145
658
|
|
|
146
659
|
if (collaborators.length > 0) {
|
|
147
|
-
console.log('
|
|
660
|
+
console.log('👥 Discovered collaborators from git history:\n');
|
|
148
661
|
collaborators.forEach((c, i) => {
|
|
149
662
|
console.log(` ${i + 1}. ${c.name} <${c.email}> (${c.commits} commits)`);
|
|
150
663
|
});
|
|
151
664
|
console.log('');
|
|
152
665
|
}
|
|
153
|
-
} catch
|
|
666
|
+
} catch {
|
|
154
667
|
console.log('ℹ️ No git history found (not a git repo or no commits)\n');
|
|
155
668
|
}
|
|
156
669
|
|
|
157
|
-
//
|
|
670
|
+
// 8. Create .mindmeld directory and config
|
|
158
671
|
await fs.mkdir(mindmeldDir, { recursive: true });
|
|
159
672
|
|
|
160
|
-
// 5. Create config.json
|
|
161
673
|
const config = {
|
|
162
|
-
projectId,
|
|
674
|
+
projectId: backendProjectId || projectId,
|
|
163
675
|
projectName,
|
|
164
|
-
|
|
676
|
+
userEmail: auth.email,
|
|
677
|
+
clientId,
|
|
678
|
+
subscriptionTier: tier,
|
|
165
679
|
collaborators,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
externalUsersAllowed: false,
|
|
169
|
-
mindmeldVersion: '3.0.0'
|
|
680
|
+
created: new Date().toISOString(),
|
|
681
|
+
mindmeldVersion: '3.1.0'
|
|
170
682
|
};
|
|
171
683
|
|
|
172
684
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
173
685
|
|
|
174
686
|
console.log('✅ MindMeld initialized!\n');
|
|
175
687
|
console.log(` Config: ${configPath}`);
|
|
176
|
-
console.log(` Project ID: ${projectId}`);
|
|
177
|
-
console.log(`
|
|
178
|
-
console.log(`
|
|
688
|
+
console.log(` Project ID: ${config.projectId}`);
|
|
689
|
+
console.log(` User: ${auth.email}`);
|
|
690
|
+
console.log(` Plan: ${tier}`);
|
|
179
691
|
console.log('');
|
|
180
692
|
|
|
181
|
-
//
|
|
693
|
+
// 9. Check for community standards
|
|
182
694
|
const standardsDir = path.join(projectPath, '.equilateral-standards');
|
|
183
|
-
|
|
184
695
|
try {
|
|
185
696
|
await fs.access(standardsDir);
|
|
186
697
|
console.log('ℹ️ Community standards (.equilateral-standards) available');
|
|
187
698
|
} catch {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
699
|
+
if (tier !== 'free') {
|
|
700
|
+
try {
|
|
701
|
+
await execAsync(
|
|
702
|
+
'git clone https://github.com/Equilateral-AI/EquilateralAgents-Community-Standards.git .equilateral-standards',
|
|
703
|
+
{ cwd: projectPath }
|
|
704
|
+
);
|
|
705
|
+
console.log('✅ Cloned community standards repo');
|
|
706
|
+
} catch {
|
|
707
|
+
console.log('ℹ️ Community standards not available (clone from:');
|
|
708
|
+
console.log(' https://github.com/Equilateral-AI/EquilateralAgents-Community-Standards)');
|
|
709
|
+
}
|
|
199
710
|
}
|
|
200
711
|
}
|
|
201
712
|
|
|
202
|
-
//
|
|
203
|
-
await bootstrapFromHistory(projectPath
|
|
713
|
+
// 10. Bootstrap from git history
|
|
714
|
+
await bootstrapFromHistory(projectPath);
|
|
204
715
|
|
|
205
|
-
//
|
|
716
|
+
// 11. Configure Claude Code hooks
|
|
206
717
|
await configureClaudeHooks(projectPath);
|
|
207
718
|
|
|
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
|
|
719
|
+
// 12. Summary
|
|
216
720
|
console.log('\n🚀 Next steps:');
|
|
217
721
|
console.log(' 1. Start a Claude Code session in this project');
|
|
218
722
|
console.log(' 2. MindMeld hooks will inject relevant standards automatically');
|
|
219
723
|
console.log(' 3. Patterns from your sessions will be harvested and validated');
|
|
220
|
-
if (
|
|
221
|
-
console.log(' 4.
|
|
724
|
+
if (tier !== 'free') {
|
|
725
|
+
console.log(' 4. Visit https://app.mindmeld.dev to manage team standards');
|
|
222
726
|
}
|
|
223
727
|
console.log('');
|
|
224
728
|
}
|
|
225
729
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
730
|
+
// ============================================================================
|
|
731
|
+
// Bootstrap & Hooks (unchanged)
|
|
732
|
+
// ============================================================================
|
|
733
|
+
|
|
734
|
+
async function bootstrapFromHistory(projectPath) {
|
|
230
735
|
console.log('\n📊 Bootstrapping from git history...\n');
|
|
231
736
|
|
|
232
737
|
try {
|
|
233
738
|
const { getGitHistory, detectPatterns, savePatterns, promotePatterns, harvestPlans, promoteDecisions } = require('./harvest');
|
|
234
739
|
|
|
235
|
-
// Get extended git history
|
|
236
740
|
const gitHistory = await getGitHistory(projectPath, { since: '90d', commits: 50 });
|
|
237
741
|
const patterns = detectPatterns(gitHistory);
|
|
238
742
|
|
|
@@ -242,10 +746,7 @@ async function bootstrapFromHistory(projectPath, options = {}) {
|
|
|
242
746
|
return;
|
|
243
747
|
}
|
|
244
748
|
|
|
245
|
-
// Save raw patterns
|
|
246
749
|
await savePatterns(projectPath, patterns);
|
|
247
|
-
|
|
248
|
-
// Promote high-confidence patterns to provisional standards
|
|
249
750
|
const promoted = await promotePatterns(projectPath, patterns, { threshold: 0.5 });
|
|
250
751
|
|
|
251
752
|
console.log(` Detected ${patterns.length} pattern(s) from git history`);
|
|
@@ -256,7 +757,6 @@ async function bootstrapFromHistory(projectPath, options = {}) {
|
|
|
256
757
|
}
|
|
257
758
|
}
|
|
258
759
|
|
|
259
|
-
// Harvest decisions from Claude Code plan files
|
|
260
760
|
const planDecisions = await harvestPlans(projectPath);
|
|
261
761
|
if (planDecisions.length > 0) {
|
|
262
762
|
const promotedDecisions = await promoteDecisions(projectPath, planDecisions);
|
|
@@ -279,20 +779,14 @@ async function bootstrapFromHistory(projectPath, options = {}) {
|
|
|
279
779
|
}
|
|
280
780
|
}
|
|
281
781
|
|
|
282
|
-
/**
|
|
283
|
-
* Configure Claude Code hooks in .claude/settings.json
|
|
284
|
-
* This wires up MindMeld's session-start and pre-compact hooks
|
|
285
|
-
*/
|
|
286
782
|
async function configureClaudeHooks(projectPath) {
|
|
287
783
|
const claudeDir = path.join(projectPath, '.claude');
|
|
288
784
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
289
785
|
|
|
290
|
-
// Resolve hook paths from installed package location
|
|
291
786
|
const packageRoot = path.resolve(__dirname, '..');
|
|
292
787
|
const sessionStartHook = path.join(packageRoot, 'hooks', 'session-start.js');
|
|
293
788
|
const preCompactHook = path.join(packageRoot, 'hooks', 'pre-compact.js');
|
|
294
789
|
|
|
295
|
-
// Verify hooks exist
|
|
296
790
|
try {
|
|
297
791
|
await fs.access(sessionStartHook);
|
|
298
792
|
await fs.access(preCompactHook);
|
|
@@ -302,50 +796,37 @@ async function configureClaudeHooks(projectPath) {
|
|
|
302
796
|
return;
|
|
303
797
|
}
|
|
304
798
|
|
|
305
|
-
// MindMeld hook definitions
|
|
306
799
|
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
|
-
]
|
|
800
|
+
SessionStart: [{
|
|
801
|
+
hooks: [{
|
|
802
|
+
type: 'command',
|
|
803
|
+
command: `node "${sessionStartHook}"`,
|
|
804
|
+
timeout: 5
|
|
805
|
+
}]
|
|
806
|
+
}],
|
|
807
|
+
PreCompact: [{
|
|
808
|
+
hooks: [{
|
|
809
|
+
type: 'command',
|
|
810
|
+
command: `node "${preCompactHook}"`,
|
|
811
|
+
timeout: 30
|
|
812
|
+
}]
|
|
813
|
+
}]
|
|
329
814
|
};
|
|
330
815
|
|
|
331
|
-
// Create .claude directory
|
|
332
816
|
await fs.mkdir(claudeDir, { recursive: true });
|
|
333
817
|
|
|
334
|
-
// Load existing settings if present
|
|
335
818
|
let settings = {};
|
|
336
819
|
try {
|
|
337
820
|
const existing = await fs.readFile(settingsPath, 'utf-8');
|
|
338
821
|
settings = JSON.parse(existing);
|
|
339
822
|
} catch {
|
|
340
|
-
// No existing settings
|
|
823
|
+
// No existing settings
|
|
341
824
|
}
|
|
342
825
|
|
|
343
|
-
// Merge hooks (preserve existing non-MindMeld hooks)
|
|
344
826
|
if (!settings.hooks) {
|
|
345
827
|
settings.hooks = {};
|
|
346
828
|
}
|
|
347
829
|
|
|
348
|
-
// Check if MindMeld hooks already configured
|
|
349
830
|
const hasSessionStart = (settings.hooks.SessionStart || []).some(h =>
|
|
350
831
|
h.hooks?.some(hk => hk.command?.includes('mindmeld') || hk.command?.includes('session-start'))
|
|
351
832
|
);
|
|
@@ -358,7 +839,6 @@ async function configureClaudeHooks(projectPath) {
|
|
|
358
839
|
return;
|
|
359
840
|
}
|
|
360
841
|
|
|
361
|
-
// Add MindMeld hooks (append to existing, don't replace)
|
|
362
842
|
if (!hasSessionStart) {
|
|
363
843
|
settings.hooks.SessionStart = [
|
|
364
844
|
...(settings.hooks.SessionStart || []),
|
|
@@ -379,6 +859,39 @@ async function configureClaudeHooks(projectPath) {
|
|
|
379
859
|
console.log(` PreCompact: ${preCompactHook}`);
|
|
380
860
|
}
|
|
381
861
|
|
|
862
|
+
// ============================================================================
|
|
863
|
+
// Utility Commands
|
|
864
|
+
// ============================================================================
|
|
865
|
+
|
|
866
|
+
async function showStatus() {
|
|
867
|
+
const auth = loadAuth();
|
|
868
|
+
|
|
869
|
+
console.log('\n🎯 MindMeld Status\n');
|
|
870
|
+
|
|
871
|
+
if (!auth) {
|
|
872
|
+
console.log(' Not authenticated.');
|
|
873
|
+
console.log(' Run "mindmeld login" to authenticate.\n');
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
console.log(` Email: ${auth.email}`);
|
|
878
|
+
console.log(` Token expires: ${new Date(auth.expires_at).toLocaleString()}`);
|
|
879
|
+
console.log(` Token status: ${isTokenExpired(auth.expires_at) ? 'Expired' : 'Valid'}`);
|
|
880
|
+
console.log(` Refresh token: ${auth.refresh_token ? 'Present' : 'Missing'}`);
|
|
881
|
+
console.log('');
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
async function logout() {
|
|
885
|
+
await clearAuth();
|
|
886
|
+
console.log('\n✅ Logged out successfully.\n');
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
async function login() {
|
|
890
|
+
console.log('\n🎯 MindMeld Login\n');
|
|
891
|
+
const auth = await browserAuth();
|
|
892
|
+
console.log(`\n✅ Logged in as ${auth.email}\n`);
|
|
893
|
+
}
|
|
894
|
+
|
|
382
895
|
async function promptYesNo(question) {
|
|
383
896
|
process.stdout.write(question);
|
|
384
897
|
|
|
@@ -390,7 +903,10 @@ async function promptYesNo(question) {
|
|
|
390
903
|
});
|
|
391
904
|
}
|
|
392
905
|
|
|
906
|
+
// ============================================================================
|
|
393
907
|
// Main
|
|
908
|
+
// ============================================================================
|
|
909
|
+
|
|
394
910
|
const args = parseArgs(process.argv.slice(2));
|
|
395
911
|
|
|
396
912
|
if (args.help && !args.command) {
|
|
@@ -400,7 +916,7 @@ if (args.help && !args.command) {
|
|
|
400
916
|
|
|
401
917
|
if (args.command === 'init') {
|
|
402
918
|
if (args.help) { showHelp(); process.exit(0); }
|
|
403
|
-
initProject(args.projectPath
|
|
919
|
+
initProject(args.projectPath)
|
|
404
920
|
.then(() => process.exit(0))
|
|
405
921
|
.catch(error => {
|
|
406
922
|
console.error('\n❌ Error:', error.message);
|
|
@@ -408,10 +924,7 @@ if (args.command === 'init') {
|
|
|
408
924
|
});
|
|
409
925
|
} else if (args.command === 'inject') {
|
|
410
926
|
const { inject, showInjectHelp } = require('./inject');
|
|
411
|
-
if (args.help) {
|
|
412
|
-
showInjectHelp();
|
|
413
|
-
process.exit(0);
|
|
414
|
-
}
|
|
927
|
+
if (args.help) { showInjectHelp(); process.exit(0); }
|
|
415
928
|
inject({ format: args.format, path: args.projectPath })
|
|
416
929
|
.then(() => process.exit(0))
|
|
417
930
|
.catch(error => {
|
|
@@ -420,16 +933,34 @@ if (args.command === 'init') {
|
|
|
420
933
|
});
|
|
421
934
|
} else if (args.command === 'harvest') {
|
|
422
935
|
const { harvest, showHarvestHelp } = require('./harvest');
|
|
423
|
-
if (args.help) {
|
|
424
|
-
showHarvestHelp();
|
|
425
|
-
process.exit(0);
|
|
426
|
-
}
|
|
936
|
+
if (args.help) { showHarvestHelp(); process.exit(0); }
|
|
427
937
|
harvest({ path: args.projectPath, since: args.since, commits: args.commits, dryRun: args.dryRun })
|
|
428
938
|
.then(() => process.exit(0))
|
|
429
939
|
.catch(error => {
|
|
430
940
|
console.error('\n❌ Error:', error.message);
|
|
431
941
|
process.exit(1);
|
|
432
942
|
});
|
|
943
|
+
} else if (args.command === 'logout') {
|
|
944
|
+
logout()
|
|
945
|
+
.then(() => process.exit(0))
|
|
946
|
+
.catch(error => {
|
|
947
|
+
console.error('\n❌ Error:', error.message);
|
|
948
|
+
process.exit(1);
|
|
949
|
+
});
|
|
950
|
+
} else if (args.command === 'login') {
|
|
951
|
+
login()
|
|
952
|
+
.then(() => process.exit(0))
|
|
953
|
+
.catch(error => {
|
|
954
|
+
console.error('\n❌ Error:', error.message);
|
|
955
|
+
process.exit(1);
|
|
956
|
+
});
|
|
957
|
+
} else if (args.command === 'status') {
|
|
958
|
+
showStatus()
|
|
959
|
+
.then(() => process.exit(0))
|
|
960
|
+
.catch(error => {
|
|
961
|
+
console.error('\n❌ Error:', error.message);
|
|
962
|
+
process.exit(1);
|
|
963
|
+
});
|
|
433
964
|
} else {
|
|
434
965
|
console.error(`Unknown command: ${args.command}`);
|
|
435
966
|
console.error('Run "mindmeld --help" for usage.');
|