slyplan-mcp 1.4.2 → 1.5.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/dist/cli.js +97 -143
- package/dist/supabase.js +13 -30
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,11 +3,11 @@ import * as fs from 'fs';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as http from 'http';
|
|
5
5
|
import * as readline from 'readline';
|
|
6
|
-
import { createClient } from '@supabase/supabase-js';
|
|
7
6
|
import { exec } from 'child_process';
|
|
7
|
+
import { createClient } from '@supabase/supabase-js';
|
|
8
8
|
const SUPABASE_URL = 'https://omfzpkwtuzucwwxmyuqt.supabase.co';
|
|
9
9
|
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9tZnpwa3d0dXp1Y3d3eG15dXF0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzA5MjMwNDIsImV4cCI6MjA4NjQ5OTA0Mn0.KXGoUez7M45RtFM9qR7mjzGX6UhlaRE-gggAJxSkIHY';
|
|
10
|
-
const
|
|
10
|
+
const SLYPLAN_ORIGIN = 'https://slyplan.com';
|
|
11
11
|
// Smart transcript-aware hook: checks if set_project + add_to_work_mode have been called.
|
|
12
12
|
// Silent when OK, reminds only when needed.
|
|
13
13
|
const PRE_HOOK_FILE_CONTENT = `#!/usr/bin/env node
|
|
@@ -218,136 +218,66 @@ You MUST keep SlyPlan updated. This is not optional. Follow these rules:
|
|
|
218
218
|
function log(msg) {
|
|
219
219
|
process.stderr.write(msg + '\n');
|
|
220
220
|
}
|
|
221
|
-
function promptUser(question
|
|
221
|
+
function promptUser(question) {
|
|
222
222
|
return new Promise((resolve) => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
stdin.resume();
|
|
229
|
-
stdin.setEncoding('utf8');
|
|
230
|
-
let password = '';
|
|
231
|
-
const onData = (ch) => {
|
|
232
|
-
const c = ch.toString();
|
|
233
|
-
if (c === '\n' || c === '\r' || c === '\u0004') {
|
|
234
|
-
stdin.setRawMode(false);
|
|
235
|
-
stdin.pause();
|
|
236
|
-
stdin.removeListener('data', onData);
|
|
237
|
-
process.stderr.write('\n');
|
|
238
|
-
resolve(password);
|
|
239
|
-
}
|
|
240
|
-
else if (c === '\u0003') {
|
|
241
|
-
// Ctrl+C
|
|
242
|
-
stdin.setRawMode(false);
|
|
243
|
-
process.exit(1);
|
|
244
|
-
}
|
|
245
|
-
else if (c === '\u007f' || c === '\b') {
|
|
246
|
-
// Backspace
|
|
247
|
-
if (password.length > 0) {
|
|
248
|
-
password = password.slice(0, -1);
|
|
249
|
-
process.stderr.write('\b \b');
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
password += c;
|
|
254
|
-
process.stderr.write('*');
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
stdin.on('data', onData);
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
// Non-TTY or visible input
|
|
261
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
262
|
-
rl.question(question, (answer) => {
|
|
263
|
-
rl.close();
|
|
264
|
-
resolve(answer.trim());
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
async function validateCredentials(email, password) {
|
|
270
|
-
const client = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
|
271
|
-
auth: { autoRefreshToken: false, persistSession: false },
|
|
223
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
224
|
+
rl.question(question, (answer) => {
|
|
225
|
+
rl.close();
|
|
226
|
+
resolve(answer.trim());
|
|
227
|
+
});
|
|
272
228
|
});
|
|
273
|
-
const { data, error } = await client.auth.signInWithPassword({ email, password });
|
|
274
|
-
if (error)
|
|
275
|
-
return { ok: false, error: error.message };
|
|
276
|
-
return { ok: true, userEmail: data.user?.email ?? email, refreshToken: data.session?.refresh_token };
|
|
277
229
|
}
|
|
278
|
-
function readJsonFile(filePath) {
|
|
279
|
-
try {
|
|
280
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
281
|
-
return JSON.parse(content);
|
|
282
|
-
}
|
|
283
|
-
catch {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
function writeJsonFile(filePath, data) {
|
|
288
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
289
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
290
|
-
}
|
|
291
|
-
// --- Browser Auth ---
|
|
292
230
|
function openBrowser(url) {
|
|
293
231
|
const platform = process.platform;
|
|
294
|
-
const cmd = platform === 'win32' ?
|
|
295
|
-
: platform === 'darwin' ?
|
|
296
|
-
:
|
|
297
|
-
exec(
|
|
232
|
+
const cmd = platform === 'win32' ? `start "" "${url}"`
|
|
233
|
+
: platform === 'darwin' ? `open "${url}"`
|
|
234
|
+
: `xdg-open "${url}"`;
|
|
235
|
+
exec(cmd, () => {
|
|
236
|
+
// Ignore errors — user can always open manually
|
|
237
|
+
});
|
|
298
238
|
}
|
|
299
239
|
const SUCCESS_HTML = `<!DOCTYPE html>
|
|
300
|
-
<html
|
|
301
|
-
<head><
|
|
240
|
+
<html>
|
|
241
|
+
<head><title>SlyPlan — Authorized</title>
|
|
302
242
|
<style>
|
|
303
|
-
body {
|
|
304
|
-
|
|
305
|
-
.card { text-align: center; padding: 3rem;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
</head>
|
|
311
|
-
<body>
|
|
312
|
-
<div class="
|
|
313
|
-
<svg class="check" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
314
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
315
|
-
</svg>
|
|
243
|
+
body { background: #09090b; color: #e4e4e7; font-family: system-ui, sans-serif;
|
|
244
|
+
display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
|
|
245
|
+
.card { text-align: center; padding: 3rem; border-radius: 12px;
|
|
246
|
+
background: #18181b; border: 1px solid #27272a; max-width: 360px; }
|
|
247
|
+
.check { color: #22c55e; font-size: 3rem; margin-bottom: 1rem; }
|
|
248
|
+
h1 { font-size: 1.25rem; margin: 0 0 0.5rem; }
|
|
249
|
+
p { color: #71717a; font-size: 0.875rem; margin: 0; }
|
|
250
|
+
</style></head>
|
|
251
|
+
<body><div class="card">
|
|
252
|
+
<div class="check">✓</div>
|
|
316
253
|
<h1>Authorized!</h1>
|
|
317
254
|
<p>You can close this tab and return to your terminal.</p>
|
|
318
|
-
</div
|
|
319
|
-
</body>
|
|
320
|
-
</html>`;
|
|
255
|
+
</div></body></html>`;
|
|
321
256
|
const ERROR_HTML = (msg) => `<!DOCTYPE html>
|
|
322
|
-
<html
|
|
323
|
-
<head><
|
|
257
|
+
<html>
|
|
258
|
+
<head><title>SlyPlan — Error</title>
|
|
324
259
|
<style>
|
|
325
|
-
body {
|
|
326
|
-
|
|
327
|
-
.card { text-align: center; padding: 3rem;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
</head>
|
|
333
|
-
<body>
|
|
334
|
-
<div class="
|
|
335
|
-
<svg class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
336
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
|
337
|
-
</svg>
|
|
260
|
+
body { background: #09090b; color: #e4e4e7; font-family: system-ui, sans-serif;
|
|
261
|
+
display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
|
|
262
|
+
.card { text-align: center; padding: 3rem; border-radius: 12px;
|
|
263
|
+
background: #18181b; border: 1px solid #27272a; max-width: 360px; }
|
|
264
|
+
.icon { color: #ef4444; font-size: 3rem; margin-bottom: 1rem; }
|
|
265
|
+
h1 { font-size: 1.25rem; margin: 0 0 0.5rem; }
|
|
266
|
+
p { color: #71717a; font-size: 0.875rem; margin: 0; }
|
|
267
|
+
</style></head>
|
|
268
|
+
<body><div class="card">
|
|
269
|
+
<div class="icon">✗</div>
|
|
338
270
|
<h1>Authorization Failed</h1>
|
|
339
271
|
<p>${msg}</p>
|
|
340
|
-
</div
|
|
341
|
-
</body>
|
|
342
|
-
</html>`;
|
|
272
|
+
</div></body></html>`;
|
|
343
273
|
function startLocalAuthServer() {
|
|
344
274
|
return new Promise((resolve, reject) => {
|
|
345
275
|
const server = http.createServer((req, res) => {
|
|
346
276
|
const url = new URL(req.url ?? '/', `http://localhost`);
|
|
347
277
|
if (url.pathname === '/callback') {
|
|
348
278
|
const refreshToken = url.searchParams.get('refresh_token');
|
|
349
|
-
const email = url.searchParams.get('email')
|
|
350
|
-
if (refreshToken) {
|
|
279
|
+
const email = url.searchParams.get('email');
|
|
280
|
+
if (refreshToken && email) {
|
|
351
281
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
352
282
|
res.end(SUCCESS_HTML);
|
|
353
283
|
server.close();
|
|
@@ -355,42 +285,65 @@ function startLocalAuthServer() {
|
|
|
355
285
|
}
|
|
356
286
|
else {
|
|
357
287
|
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
358
|
-
res.end(ERROR_HTML('Missing
|
|
288
|
+
res.end(ERROR_HTML('Missing token or email in callback.'));
|
|
359
289
|
server.close();
|
|
360
|
-
reject(new Error('Missing
|
|
290
|
+
reject(new Error('Missing token or email in callback'));
|
|
361
291
|
}
|
|
362
292
|
}
|
|
363
293
|
else {
|
|
364
|
-
res.writeHead(404
|
|
365
|
-
res.end(
|
|
294
|
+
res.writeHead(404);
|
|
295
|
+
res.end();
|
|
366
296
|
}
|
|
367
297
|
});
|
|
368
|
-
// Listen on random port
|
|
369
298
|
server.listen(0, '127.0.0.1', () => {
|
|
370
299
|
const addr = server.address();
|
|
371
300
|
if (!addr || typeof addr === 'string') {
|
|
372
|
-
reject(new Error('Failed to
|
|
301
|
+
reject(new Error('Failed to get server address'));
|
|
373
302
|
return;
|
|
374
303
|
}
|
|
375
304
|
const port = addr.port;
|
|
376
305
|
const callbackUrl = `http://localhost:${port}/callback`;
|
|
377
|
-
const authUrl = `${
|
|
378
|
-
log(` Opening browser...`);
|
|
379
|
-
log(` If it doesn't open, visit
|
|
380
|
-
log(` ${authUrl}`);
|
|
306
|
+
const authUrl = `${SLYPLAN_ORIGIN}/cli/auth?port=${port}&callback=${encodeURIComponent(callbackUrl)}`;
|
|
307
|
+
log(` Opening browser to authorize...`);
|
|
308
|
+
log(` If it doesn't open, visit: ${authUrl}`);
|
|
381
309
|
log('');
|
|
382
310
|
openBrowser(authUrl);
|
|
383
311
|
});
|
|
384
312
|
// Timeout after 120 seconds
|
|
385
313
|
setTimeout(() => {
|
|
386
314
|
server.close();
|
|
387
|
-
reject(new Error('Authorization timed out
|
|
315
|
+
reject(new Error('Authorization timed out (120s). Try again.'));
|
|
388
316
|
}, 120000);
|
|
389
317
|
});
|
|
390
318
|
}
|
|
319
|
+
async function validateCredentials(email, password) {
|
|
320
|
+
const client = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
|
321
|
+
auth: { autoRefreshToken: false, persistSession: false },
|
|
322
|
+
});
|
|
323
|
+
const { data, error } = await client.auth.signInWithPassword({ email, password });
|
|
324
|
+
if (error)
|
|
325
|
+
return { ok: false, error: error.message };
|
|
326
|
+
return {
|
|
327
|
+
ok: true,
|
|
328
|
+
userEmail: data.user?.email ?? email,
|
|
329
|
+
refreshToken: data.session?.refresh_token,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function readJsonFile(filePath) {
|
|
333
|
+
try {
|
|
334
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
335
|
+
return JSON.parse(content);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function writeJsonFile(filePath, data) {
|
|
342
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
343
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
344
|
+
}
|
|
391
345
|
// --- Settings Merge ---
|
|
392
346
|
// MCP server config goes in .mcp.json (Claude Code reads servers from here)
|
|
393
|
-
// Hooks go in .claude/settings.json
|
|
394
347
|
function mergeMcpJson(existing, refreshToken) {
|
|
395
348
|
const result = { ...existing };
|
|
396
349
|
if (!result.mcpServers)
|
|
@@ -404,8 +357,15 @@ function mergeMcpJson(existing, refreshToken) {
|
|
|
404
357
|
};
|
|
405
358
|
return result;
|
|
406
359
|
}
|
|
360
|
+
// Hooks go in .claude/settings.json
|
|
407
361
|
function mergeSettings(existing) {
|
|
408
362
|
const result = { ...existing };
|
|
363
|
+
// Remove any stale MCP server config (moved to .mcp.json)
|
|
364
|
+
if (result.mcpServers?.slyplan) {
|
|
365
|
+
delete result.mcpServers.slyplan;
|
|
366
|
+
if (Object.keys(result.mcpServers).length === 0)
|
|
367
|
+
delete result.mcpServers;
|
|
368
|
+
}
|
|
409
369
|
// Hook config
|
|
410
370
|
if (!result.hooks)
|
|
411
371
|
result.hooks = {};
|
|
@@ -460,8 +420,8 @@ function removeFromMcpJson(existing) {
|
|
|
460
420
|
}
|
|
461
421
|
function removeFromSettings(existing) {
|
|
462
422
|
const result = { ...existing };
|
|
463
|
-
// Remove MCP server
|
|
464
|
-
if (result.mcpServers) {
|
|
423
|
+
// Remove any stale MCP server config
|
|
424
|
+
if (result.mcpServers?.slyplan) {
|
|
465
425
|
delete result.mcpServers.slyplan;
|
|
466
426
|
if (Object.keys(result.mcpServers).length === 0)
|
|
467
427
|
delete result.mcpServers;
|
|
@@ -486,8 +446,8 @@ async function runSetup() {
|
|
|
486
446
|
const cwd = process.cwd();
|
|
487
447
|
const claudeDir = path.join(cwd, '.claude');
|
|
488
448
|
const hooksDir = path.join(claudeDir, 'hooks');
|
|
489
|
-
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
490
449
|
const mcpJsonPath = path.join(cwd, '.mcp.json');
|
|
450
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
491
451
|
const postHookFilePath = path.join(hooksDir, 'slyplan-post-tool-sync.cjs');
|
|
492
452
|
const preHookFilePath = path.join(hooksDir, 'slyplan-pre-tool-sync.cjs');
|
|
493
453
|
const claudeMdPath = path.join(cwd, 'CLAUDE.md');
|
|
@@ -496,7 +456,7 @@ async function runSetup() {
|
|
|
496
456
|
log(' ==============================');
|
|
497
457
|
log(` Setting up in: ${cwd}`);
|
|
498
458
|
log('');
|
|
499
|
-
// Parse flags
|
|
459
|
+
// Parse flags (--email + --password for CI/scripted use)
|
|
500
460
|
const args = process.argv.slice(3);
|
|
501
461
|
let flagEmail = '';
|
|
502
462
|
let flagPassword = '';
|
|
@@ -509,7 +469,7 @@ async function runSetup() {
|
|
|
509
469
|
let refreshToken = '';
|
|
510
470
|
let userEmail = '';
|
|
511
471
|
if (flagEmail && flagPassword) {
|
|
512
|
-
// CI
|
|
472
|
+
// CI mode: validate credentials and extract refresh token
|
|
513
473
|
process.stderr.write(' Validating credentials... ');
|
|
514
474
|
const result = await validateCredentials(flagEmail, flagPassword);
|
|
515
475
|
if (!result.ok) {
|
|
@@ -526,8 +486,6 @@ async function runSetup() {
|
|
|
526
486
|
}
|
|
527
487
|
else {
|
|
528
488
|
// Interactive mode: browser-based auth
|
|
529
|
-
log(' Waiting for authorization in browser...');
|
|
530
|
-
log('');
|
|
531
489
|
try {
|
|
532
490
|
const result = await startLocalAuthServer();
|
|
533
491
|
refreshToken = result.refreshToken;
|
|
@@ -535,7 +493,6 @@ async function runSetup() {
|
|
|
535
493
|
log(` Authorized as ${userEmail}`);
|
|
536
494
|
}
|
|
537
495
|
catch (err) {
|
|
538
|
-
log('');
|
|
539
496
|
log(` ${err.message}`);
|
|
540
497
|
process.exit(1);
|
|
541
498
|
}
|
|
@@ -547,15 +504,12 @@ async function runSetup() {
|
|
|
547
504
|
log(' [+] Created .claude/hooks/slyplan-pre-tool-sync.cjs');
|
|
548
505
|
fs.writeFileSync(postHookFilePath, POST_HOOK_FILE_CONTENT, 'utf8');
|
|
549
506
|
log(' [+] Created .claude/hooks/slyplan-post-tool-sync.cjs');
|
|
550
|
-
//
|
|
551
|
-
|
|
552
|
-
const rawMcp = readJsonFile(mcpJsonPath);
|
|
553
|
-
if (rawMcp)
|
|
554
|
-
existingMcp = rawMcp;
|
|
507
|
+
// 2. Write MCP server config to .mcp.json
|
|
508
|
+
const existingMcp = readJsonFile(mcpJsonPath) ?? {};
|
|
555
509
|
const mergedMcp = mergeMcpJson(existingMcp, refreshToken);
|
|
556
510
|
writeJsonFile(mcpJsonPath, mergedMcp);
|
|
557
|
-
log(' [+] Updated .mcp.json
|
|
558
|
-
//
|
|
511
|
+
log(' [+] Updated .mcp.json');
|
|
512
|
+
// 3. Write hooks to .claude/settings.json
|
|
559
513
|
let existingSettings = {};
|
|
560
514
|
const rawSettings = readJsonFile(settingsPath);
|
|
561
515
|
if (rawSettings === null && fs.existsSync(settingsPath)) {
|
|
@@ -571,7 +525,7 @@ async function runSetup() {
|
|
|
571
525
|
const mergedSettings = mergeSettings(existingSettings);
|
|
572
526
|
writeJsonFile(settingsPath, mergedSettings);
|
|
573
527
|
log(' [+] Updated .claude/settings.json (hooks)');
|
|
574
|
-
//
|
|
528
|
+
// 4. Optional CLAUDE.md append
|
|
575
529
|
const appendAnswer = await promptUser(' Append SlyPlan sync instructions to CLAUDE.md? [Y/n]: ');
|
|
576
530
|
if (appendAnswer.toLowerCase() !== 'n') {
|
|
577
531
|
if (fs.existsSync(claudeMdPath)) {
|
|
@@ -606,8 +560,8 @@ async function runSetup() {
|
|
|
606
560
|
async function runRemove() {
|
|
607
561
|
const cwd = process.cwd();
|
|
608
562
|
const claudeDir = path.join(cwd, '.claude');
|
|
609
|
-
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
610
563
|
const mcpJsonPath = path.join(cwd, '.mcp.json');
|
|
564
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
611
565
|
const postHookFilePath = path.join(claudeDir, 'hooks', 'slyplan-post-tool-sync.cjs');
|
|
612
566
|
const preHookFilePath = path.join(claudeDir, 'hooks', 'slyplan-pre-tool-sync.cjs');
|
|
613
567
|
log('');
|
|
@@ -628,7 +582,7 @@ async function runRemove() {
|
|
|
628
582
|
log(` [=] .claude/hooks/${name} not found — skipped`);
|
|
629
583
|
}
|
|
630
584
|
}
|
|
631
|
-
//
|
|
585
|
+
// 2. Clean .mcp.json
|
|
632
586
|
const existingMcp = readJsonFile(mcpJsonPath);
|
|
633
587
|
if (existingMcp) {
|
|
634
588
|
const cleanedMcp = removeFromMcpJson(existingMcp);
|
|
@@ -641,7 +595,7 @@ async function runRemove() {
|
|
|
641
595
|
log(' [-] Removed SlyPlan from .mcp.json');
|
|
642
596
|
}
|
|
643
597
|
}
|
|
644
|
-
//
|
|
598
|
+
// 3. Clean settings.json
|
|
645
599
|
const existing = readJsonFile(settingsPath);
|
|
646
600
|
if (existing) {
|
|
647
601
|
const cleaned = removeFromSettings(existing);
|
package/dist/supabase.js
CHANGED
|
@@ -7,51 +7,34 @@ export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
|
|
7
7
|
auth: { autoRefreshToken: true, persistSession: false },
|
|
8
8
|
});
|
|
9
9
|
let userId = null;
|
|
10
|
-
function findMcpJsonPath() {
|
|
11
|
-
// Walk up from cwd looking for .mcp.json
|
|
12
|
-
let dir = process.cwd();
|
|
13
|
-
while (true) {
|
|
14
|
-
const candidate = path.join(dir, '.mcp.json');
|
|
15
|
-
if (fs.existsSync(candidate))
|
|
16
|
-
return candidate;
|
|
17
|
-
const parent = path.dirname(dir);
|
|
18
|
-
if (parent === dir)
|
|
19
|
-
break;
|
|
20
|
-
dir = parent;
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
10
|
function persistNewRefreshToken(newToken) {
|
|
11
|
+
const mcpJsonPath = path.join(process.cwd(), '.mcp.json');
|
|
25
12
|
try {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!env)
|
|
33
|
-
return;
|
|
34
|
-
env.SLYPLAN_REFRESH_TOKEN = newToken;
|
|
35
|
-
fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
13
|
+
const content = fs.readFileSync(mcpJsonPath, 'utf8');
|
|
14
|
+
const config = JSON.parse(content);
|
|
15
|
+
if (config?.mcpServers?.slyplan?.env) {
|
|
16
|
+
config.mcpServers.slyplan.env.SLYPLAN_REFRESH_TOKEN = newToken;
|
|
17
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
18
|
+
}
|
|
36
19
|
}
|
|
37
20
|
catch {
|
|
38
|
-
//
|
|
21
|
+
// Best-effort — don't crash if file is missing/malformed
|
|
39
22
|
}
|
|
40
23
|
}
|
|
41
24
|
export async function authenticate() {
|
|
42
25
|
const refreshToken = process.env.SLYPLAN_REFRESH_TOKEN;
|
|
43
26
|
const email = process.env.SLYPLAN_EMAIL;
|
|
44
27
|
const password = process.env.SLYPLAN_PASSWORD;
|
|
45
|
-
// Prefer refresh token (browser
|
|
28
|
+
// Prefer refresh token (new browser-based flow)
|
|
46
29
|
if (refreshToken) {
|
|
47
30
|
const { data, error } = await supabase.auth.refreshSession({ refresh_token: refreshToken });
|
|
48
31
|
if (error) {
|
|
49
32
|
console.error(`Token refresh failed: ${error.message}`);
|
|
50
|
-
console.error('
|
|
33
|
+
console.error('Run "npx slyplan-mcp setup" to re-authenticate.');
|
|
51
34
|
process.exit(1);
|
|
52
35
|
}
|
|
53
36
|
userId = data.user.id;
|
|
54
|
-
//
|
|
37
|
+
// Self-healing: persist rotated token
|
|
55
38
|
if (data.session?.refresh_token && data.session.refresh_token !== refreshToken) {
|
|
56
39
|
persistNewRefreshToken(data.session.refresh_token);
|
|
57
40
|
}
|
|
@@ -67,8 +50,8 @@ export async function authenticate() {
|
|
|
67
50
|
userId = data.user.id;
|
|
68
51
|
return;
|
|
69
52
|
}
|
|
70
|
-
console.error('Missing SLYPLAN_REFRESH_TOKEN (or
|
|
71
|
-
console.error('Run "npx slyplan-mcp setup" to
|
|
53
|
+
console.error('Missing SLYPLAN_REFRESH_TOKEN (or SLYPLAN_EMAIL + SLYPLAN_PASSWORD).');
|
|
54
|
+
console.error('Run "npx slyplan-mcp setup" to configure authentication.');
|
|
72
55
|
process.exit(1);
|
|
73
56
|
}
|
|
74
57
|
export function getUserId() {
|