slyplan-mcp 1.4.1 → 1.5.0
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 +87 -173
- package/dist/supabase.js +13 -33
- 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
|
-
}
|
|
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
229
|
}
|
|
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,45 +285,67 @@ 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
|
-
|
|
393
|
-
// Hooks go in settings.json (can be committed)
|
|
394
|
-
function mergeLocalSettings(existing, refreshToken) {
|
|
346
|
+
function mergeSettings(existing, refreshToken) {
|
|
395
347
|
const result = { ...existing };
|
|
396
|
-
// MCP server config
|
|
348
|
+
// MCP server config
|
|
397
349
|
if (!result.mcpServers)
|
|
398
350
|
result.mcpServers = {};
|
|
399
351
|
result.mcpServers.slyplan = {
|
|
@@ -403,10 +355,6 @@ function mergeLocalSettings(existing, refreshToken) {
|
|
|
403
355
|
SLYPLAN_REFRESH_TOKEN: refreshToken,
|
|
404
356
|
},
|
|
405
357
|
};
|
|
406
|
-
return result;
|
|
407
|
-
}
|
|
408
|
-
function mergeSettings(existing) {
|
|
409
|
-
const result = { ...existing };
|
|
410
358
|
// Hook config
|
|
411
359
|
if (!result.hooks)
|
|
412
360
|
result.hooks = {};
|
|
@@ -450,18 +398,9 @@ function mergeSettings(existing) {
|
|
|
450
398
|
}
|
|
451
399
|
return result;
|
|
452
400
|
}
|
|
453
|
-
function removeFromLocalSettings(existing) {
|
|
454
|
-
const result = { ...existing };
|
|
455
|
-
if (result.mcpServers) {
|
|
456
|
-
delete result.mcpServers.slyplan;
|
|
457
|
-
if (Object.keys(result.mcpServers).length === 0)
|
|
458
|
-
delete result.mcpServers;
|
|
459
|
-
}
|
|
460
|
-
return result;
|
|
461
|
-
}
|
|
462
401
|
function removeFromSettings(existing) {
|
|
463
402
|
const result = { ...existing };
|
|
464
|
-
// Remove MCP server
|
|
403
|
+
// Remove MCP server
|
|
465
404
|
if (result.mcpServers) {
|
|
466
405
|
delete result.mcpServers.slyplan;
|
|
467
406
|
if (Object.keys(result.mcpServers).length === 0)
|
|
@@ -488,7 +427,6 @@ async function runSetup() {
|
|
|
488
427
|
const claudeDir = path.join(cwd, '.claude');
|
|
489
428
|
const hooksDir = path.join(claudeDir, 'hooks');
|
|
490
429
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
491
|
-
const localSettingsPath = path.join(claudeDir, 'settings.local.json');
|
|
492
430
|
const postHookFilePath = path.join(hooksDir, 'slyplan-post-tool-sync.cjs');
|
|
493
431
|
const preHookFilePath = path.join(hooksDir, 'slyplan-pre-tool-sync.cjs');
|
|
494
432
|
const claudeMdPath = path.join(cwd, 'CLAUDE.md');
|
|
@@ -497,7 +435,7 @@ async function runSetup() {
|
|
|
497
435
|
log(' ==============================');
|
|
498
436
|
log(` Setting up in: ${cwd}`);
|
|
499
437
|
log('');
|
|
500
|
-
// Parse flags
|
|
438
|
+
// Parse flags (--email + --password for CI/scripted use)
|
|
501
439
|
const args = process.argv.slice(3);
|
|
502
440
|
let flagEmail = '';
|
|
503
441
|
let flagPassword = '';
|
|
@@ -510,7 +448,7 @@ async function runSetup() {
|
|
|
510
448
|
let refreshToken = '';
|
|
511
449
|
let userEmail = '';
|
|
512
450
|
if (flagEmail && flagPassword) {
|
|
513
|
-
// CI
|
|
451
|
+
// CI mode: validate credentials and extract refresh token
|
|
514
452
|
process.stderr.write(' Validating credentials... ');
|
|
515
453
|
const result = await validateCredentials(flagEmail, flagPassword);
|
|
516
454
|
if (!result.ok) {
|
|
@@ -527,8 +465,6 @@ async function runSetup() {
|
|
|
527
465
|
}
|
|
528
466
|
else {
|
|
529
467
|
// Interactive mode: browser-based auth
|
|
530
|
-
log(' Waiting for authorization in browser...');
|
|
531
|
-
log('');
|
|
532
468
|
try {
|
|
533
469
|
const result = await startLocalAuthServer();
|
|
534
470
|
refreshToken = result.refreshToken;
|
|
@@ -536,7 +472,6 @@ async function runSetup() {
|
|
|
536
472
|
log(` Authorized as ${userEmail}`);
|
|
537
473
|
}
|
|
538
474
|
catch (err) {
|
|
539
|
-
log('');
|
|
540
475
|
log(` ${err.message}`);
|
|
541
476
|
process.exit(1);
|
|
542
477
|
}
|
|
@@ -548,18 +483,11 @@ async function runSetup() {
|
|
|
548
483
|
log(' [+] Created .claude/hooks/slyplan-pre-tool-sync.cjs');
|
|
549
484
|
fs.writeFileSync(postHookFilePath, POST_HOOK_FILE_CONTENT, 'utf8');
|
|
550
485
|
log(' [+] Created .claude/hooks/slyplan-post-tool-sync.cjs');
|
|
551
|
-
//
|
|
552
|
-
let existingLocal = {};
|
|
553
|
-
const rawLocal = readJsonFile(localSettingsPath);
|
|
554
|
-
if (rawLocal)
|
|
555
|
-
existingLocal = rawLocal;
|
|
556
|
-
const mergedLocal = mergeLocalSettings(existingLocal, refreshToken);
|
|
557
|
-
writeJsonFile(localSettingsPath, mergedLocal);
|
|
558
|
-
log(' [+] Updated .claude/settings.local.json (MCP server)');
|
|
559
|
-
// 2b. Merge settings.json (hooks — safe to commit)
|
|
486
|
+
// 2. Merge settings.json
|
|
560
487
|
let existingSettings = {};
|
|
561
488
|
const rawSettings = readJsonFile(settingsPath);
|
|
562
489
|
if (rawSettings === null && fs.existsSync(settingsPath)) {
|
|
490
|
+
// File exists but is malformed
|
|
563
491
|
const answer = await promptUser(' .claude/settings.json exists but is malformed. Overwrite? [y/N]: ');
|
|
564
492
|
if (answer.toLowerCase() !== 'y') {
|
|
565
493
|
log(' Aborting. Fix settings.json manually and re-run setup.');
|
|
@@ -569,9 +497,9 @@ async function runSetup() {
|
|
|
569
497
|
else if (rawSettings) {
|
|
570
498
|
existingSettings = rawSettings;
|
|
571
499
|
}
|
|
572
|
-
const mergedSettings = mergeSettings(existingSettings);
|
|
500
|
+
const mergedSettings = mergeSettings(existingSettings, refreshToken);
|
|
573
501
|
writeJsonFile(settingsPath, mergedSettings);
|
|
574
|
-
log(' [+] Updated .claude/settings.json
|
|
502
|
+
log(' [+] Updated .claude/settings.json');
|
|
575
503
|
// 3. Optional CLAUDE.md append
|
|
576
504
|
const appendAnswer = await promptUser(' Append SlyPlan sync instructions to CLAUDE.md? [Y/n]: ');
|
|
577
505
|
if (appendAnswer.toLowerCase() !== 'n') {
|
|
@@ -608,7 +536,6 @@ async function runRemove() {
|
|
|
608
536
|
const cwd = process.cwd();
|
|
609
537
|
const claudeDir = path.join(cwd, '.claude');
|
|
610
538
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
611
|
-
const localSettingsPath = path.join(claudeDir, 'settings.local.json');
|
|
612
539
|
const postHookFilePath = path.join(claudeDir, 'hooks', 'slyplan-post-tool-sync.cjs');
|
|
613
540
|
const preHookFilePath = path.join(claudeDir, 'hooks', 'slyplan-pre-tool-sync.cjs');
|
|
614
541
|
log('');
|
|
@@ -629,20 +556,7 @@ async function runRemove() {
|
|
|
629
556
|
log(` [=] .claude/hooks/${name} not found — skipped`);
|
|
630
557
|
}
|
|
631
558
|
}
|
|
632
|
-
//
|
|
633
|
-
const existingLocal = readJsonFile(localSettingsPath);
|
|
634
|
-
if (existingLocal) {
|
|
635
|
-
const cleanedLocal = removeFromLocalSettings(existingLocal);
|
|
636
|
-
if (Object.keys(cleanedLocal).length === 0) {
|
|
637
|
-
fs.unlinkSync(localSettingsPath);
|
|
638
|
-
log(' [-] Deleted empty .claude/settings.local.json');
|
|
639
|
-
}
|
|
640
|
-
else {
|
|
641
|
-
writeJsonFile(localSettingsPath, cleanedLocal);
|
|
642
|
-
log(' [-] Removed SlyPlan from .claude/settings.local.json');
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
// 2b. Clean settings.json (hooks + legacy MCP server)
|
|
559
|
+
// 2. Clean settings.json
|
|
646
560
|
const existing = readJsonFile(settingsPath);
|
|
647
561
|
if (existing) {
|
|
648
562
|
const cleaned = removeFromSettings(existing);
|
package/dist/supabase.js
CHANGED
|
@@ -7,54 +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 findSettingsPath() {
|
|
11
|
-
// Walk up from cwd looking for .claude/settings.local.json (preferred) or settings.json
|
|
12
|
-
let dir = process.cwd();
|
|
13
|
-
while (true) {
|
|
14
|
-
const localCandidate = path.join(dir, '.claude', 'settings.local.json');
|
|
15
|
-
if (fs.existsSync(localCandidate))
|
|
16
|
-
return localCandidate;
|
|
17
|
-
const candidate = path.join(dir, '.claude', 'settings.json');
|
|
18
|
-
if (fs.existsSync(candidate))
|
|
19
|
-
return candidate;
|
|
20
|
-
const parent = path.dirname(dir);
|
|
21
|
-
if (parent === dir)
|
|
22
|
-
break;
|
|
23
|
-
dir = parent;
|
|
24
|
-
}
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
10
|
function persistNewRefreshToken(newToken) {
|
|
11
|
+
const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
|
|
28
12
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!env)
|
|
36
|
-
return;
|
|
37
|
-
env.SLYPLAN_REFRESH_TOKEN = newToken;
|
|
38
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
13
|
+
const content = fs.readFileSync(settingsPath, 'utf8');
|
|
14
|
+
const settings = JSON.parse(content);
|
|
15
|
+
if (settings?.mcpServers?.slyplan?.env) {
|
|
16
|
+
settings.mcpServers.slyplan.env.SLYPLAN_REFRESH_TOKEN = newToken;
|
|
17
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
18
|
+
}
|
|
39
19
|
}
|
|
40
20
|
catch {
|
|
41
|
-
//
|
|
21
|
+
// Best-effort — don't crash if settings file is missing/malformed
|
|
42
22
|
}
|
|
43
23
|
}
|
|
44
24
|
export async function authenticate() {
|
|
45
25
|
const refreshToken = process.env.SLYPLAN_REFRESH_TOKEN;
|
|
46
26
|
const email = process.env.SLYPLAN_EMAIL;
|
|
47
27
|
const password = process.env.SLYPLAN_PASSWORD;
|
|
48
|
-
// Prefer refresh token (browser
|
|
28
|
+
// Prefer refresh token (new browser-based flow)
|
|
49
29
|
if (refreshToken) {
|
|
50
30
|
const { data, error } = await supabase.auth.refreshSession({ refresh_token: refreshToken });
|
|
51
31
|
if (error) {
|
|
52
32
|
console.error(`Token refresh failed: ${error.message}`);
|
|
53
|
-
console.error('
|
|
33
|
+
console.error('Run "npx slyplan-mcp setup" to re-authenticate.');
|
|
54
34
|
process.exit(1);
|
|
55
35
|
}
|
|
56
36
|
userId = data.user.id;
|
|
57
|
-
//
|
|
37
|
+
// Self-healing: persist rotated token
|
|
58
38
|
if (data.session?.refresh_token && data.session.refresh_token !== refreshToken) {
|
|
59
39
|
persistNewRefreshToken(data.session.refresh_token);
|
|
60
40
|
}
|
|
@@ -70,8 +50,8 @@ export async function authenticate() {
|
|
|
70
50
|
userId = data.user.id;
|
|
71
51
|
return;
|
|
72
52
|
}
|
|
73
|
-
console.error('Missing SLYPLAN_REFRESH_TOKEN (or
|
|
74
|
-
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.');
|
|
75
55
|
process.exit(1);
|
|
76
56
|
}
|
|
77
57
|
export function getUserId() {
|