suparank 1.3.4 → 1.4.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/bin/suparank.js +194 -30
- package/package.json +1 -1
package/bin/suparank.js
CHANGED
|
@@ -178,10 +178,11 @@ function prompt(question) {
|
|
|
178
178
|
})
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
const SUPARANK_API_URL = 'https://api.suparank.io'
|
|
182
|
+
|
|
183
|
+
async function testConnection(apiKey, projectSlug) {
|
|
182
184
|
try {
|
|
183
|
-
const
|
|
184
|
-
const response = await fetch(`${url}/projects/${projectSlug}`, {
|
|
185
|
+
const response = await fetch(`${SUPARANK_API_URL}/projects/${projectSlug}`, {
|
|
185
186
|
headers: {
|
|
186
187
|
'Authorization': `Bearer ${apiKey}`,
|
|
187
188
|
'Content-Type': 'application/json'
|
|
@@ -202,16 +203,160 @@ async function testConnection(apiKey, projectSlug, apiUrl = null) {
|
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
// Helper to sleep for a given number of milliseconds
|
|
207
|
+
function sleep(ms) {
|
|
208
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Helper to open URL in browser
|
|
212
|
+
function openBrowser(url) {
|
|
213
|
+
const { exec } = require('child_process')
|
|
214
|
+
const platform = process.platform
|
|
215
|
+
|
|
216
|
+
let command
|
|
217
|
+
if (platform === 'darwin') {
|
|
218
|
+
command = `open "${url}"`
|
|
219
|
+
} else if (platform === 'win32') {
|
|
220
|
+
command = `start "" "${url}"`
|
|
221
|
+
} else {
|
|
222
|
+
command = `xdg-open "${url}"`
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return new Promise((resolve) => {
|
|
226
|
+
exec(command, (error) => {
|
|
227
|
+
resolve(!error)
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Device authorization flow
|
|
233
|
+
async function runDeviceAuthSetup() {
|
|
234
|
+
log('Getting authorization code...', 'yellow')
|
|
235
|
+
|
|
236
|
+
// Request device code from API
|
|
237
|
+
let deviceResponse
|
|
238
|
+
try {
|
|
239
|
+
const response = await fetch(`${SUPARANK_API_URL}/auth/device`, {
|
|
240
|
+
method: 'POST'
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
throw new Error(`HTTP ${response.status}`)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
deviceResponse = await response.json()
|
|
248
|
+
} catch (e) {
|
|
249
|
+
log(`Failed to get authorization code: ${e.message}`, 'red')
|
|
250
|
+
log('Please check your internet connection and try again.', 'dim')
|
|
251
|
+
process.exit(1)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const { device_code, user_code, verification_uri_complete, expires_in, interval } = deviceResponse
|
|
255
|
+
|
|
256
|
+
// Display code to user
|
|
257
|
+
console.log()
|
|
258
|
+
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'dim')
|
|
259
|
+
console.log()
|
|
260
|
+
log(` Your code: ${colors.bright}${colors.cyan}${user_code}${colors.reset}`, 'reset')
|
|
261
|
+
console.log()
|
|
262
|
+
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'dim')
|
|
263
|
+
console.log()
|
|
264
|
+
log('Open this URL to authorize:', 'dim')
|
|
265
|
+
log(` ${verification_uri_complete}`, 'cyan')
|
|
266
|
+
console.log()
|
|
267
|
+
|
|
268
|
+
// Try to open browser
|
|
269
|
+
const openChoice = await prompt('Press Enter to open browser (or "n" to skip): ')
|
|
270
|
+
if (openChoice.toLowerCase() !== 'n') {
|
|
271
|
+
const opened = await openBrowser(verification_uri_complete)
|
|
272
|
+
if (opened) {
|
|
273
|
+
log('Browser opened!', 'green')
|
|
274
|
+
} else {
|
|
275
|
+
log('Could not open browser. Please open the URL manually.', 'yellow')
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Poll for authorization
|
|
280
|
+
console.log()
|
|
281
|
+
log('Waiting for authorization...', 'yellow')
|
|
282
|
+
log(`(Code expires in ${Math.floor(expires_in / 60)} minutes)`, 'dim')
|
|
283
|
+
console.log()
|
|
284
|
+
|
|
285
|
+
const startTime = Date.now()
|
|
286
|
+
const expiresAt = startTime + (expires_in * 1000)
|
|
287
|
+
let dots = 0
|
|
288
|
+
|
|
289
|
+
while (Date.now() < expiresAt) {
|
|
290
|
+
await sleep(interval * 1000)
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const pollResponse = await fetch(`${SUPARANK_API_URL}/auth/device/${device_code}`)
|
|
294
|
+
const result = await pollResponse.json()
|
|
295
|
+
|
|
296
|
+
if (result.error === 'authorization_pending') {
|
|
297
|
+
// Show progress
|
|
298
|
+
dots = (dots + 1) % 4
|
|
299
|
+
process.stdout.write(`\r Waiting${'.'.repeat(dots)}${' '.repeat(3 - dots)}`)
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (result.error === 'slow_down') {
|
|
304
|
+
await sleep(interval * 1000) // Double wait
|
|
305
|
+
continue
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (result.error === 'expired_token') {
|
|
309
|
+
console.log()
|
|
310
|
+
log('Authorization code expired. Please run setup again.', 'red')
|
|
311
|
+
process.exit(1)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (result.error === 'access_denied') {
|
|
315
|
+
console.log()
|
|
316
|
+
log('Authorization was denied.', 'red')
|
|
317
|
+
process.exit(1)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Success!
|
|
321
|
+
console.log()
|
|
322
|
+
console.log()
|
|
323
|
+
log('Authorization successful!', 'green')
|
|
324
|
+
console.log()
|
|
325
|
+
|
|
326
|
+
// Save config
|
|
327
|
+
const config = {
|
|
328
|
+
api_key: result.api_key,
|
|
329
|
+
project_slug: result.project_slug,
|
|
330
|
+
created_at: new Date().toISOString()
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
saveConfig(config)
|
|
334
|
+
|
|
335
|
+
log(`Project: ${result.project_name}`, 'cyan')
|
|
336
|
+
if (result.user_email) {
|
|
337
|
+
log(`User: ${result.user_email}`, 'dim')
|
|
338
|
+
}
|
|
339
|
+
log('Configuration saved!', 'green')
|
|
340
|
+
|
|
341
|
+
return true
|
|
342
|
+
|
|
343
|
+
} catch (e) {
|
|
344
|
+
// Network error, continue polling
|
|
345
|
+
dots = (dots + 1) % 4
|
|
346
|
+
process.stdout.write(`\r Waiting${'.'.repeat(dots)}${' '.repeat(3 - dots)}`)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
207
349
|
|
|
208
|
-
log('Welcome to Suparank!', 'cyan')
|
|
209
|
-
log('This wizard will help you configure your MCP client.', 'dim')
|
|
210
350
|
console.log()
|
|
351
|
+
log('Authorization timed out. Please run setup again.', 'red')
|
|
352
|
+
process.exit(1)
|
|
353
|
+
}
|
|
211
354
|
|
|
355
|
+
// Manual setup flow (fallback)
|
|
356
|
+
async function runManualSetup() {
|
|
212
357
|
// Step 1: Get API key
|
|
213
358
|
log('Step 1: API Key', 'bright')
|
|
214
|
-
log('Get your API key from: https://suparank.io/dashboard/settings/api-keys', 'dim')
|
|
359
|
+
log('Get your API key from: https://app.suparank.io/dashboard/settings/api-keys', 'dim')
|
|
215
360
|
console.log()
|
|
216
361
|
|
|
217
362
|
const apiKey = await prompt('Enter your API key: ')
|
|
@@ -232,25 +377,15 @@ async function runSetup() {
|
|
|
232
377
|
process.exit(1)
|
|
233
378
|
}
|
|
234
379
|
|
|
235
|
-
// Step 3: API URL (optional)
|
|
236
|
-
console.log()
|
|
237
|
-
log('Step 3: API URL (optional)', 'bright')
|
|
238
|
-
log('Press Enter for default, or enter custom URL for self-hosted/development', 'dim')
|
|
239
|
-
const defaultUrl = process.env.SUPARANK_API_URL || 'http://localhost:3000'
|
|
240
|
-
console.log()
|
|
241
|
-
|
|
242
|
-
const apiUrlInput = await prompt(`API URL [${defaultUrl}]: `)
|
|
243
|
-
const apiUrl = apiUrlInput || defaultUrl
|
|
244
|
-
|
|
245
380
|
// Test connection
|
|
246
381
|
console.log()
|
|
247
382
|
log('Testing connection...', 'yellow')
|
|
248
383
|
|
|
249
|
-
const result = await testConnection(apiKey, projectSlug
|
|
384
|
+
const result = await testConnection(apiKey, projectSlug)
|
|
250
385
|
|
|
251
386
|
if (!result.success) {
|
|
252
387
|
log(`Connection failed: ${result.error}`, 'red')
|
|
253
|
-
log('Please check your API key
|
|
388
|
+
log('Please check your API key and project slug.', 'dim')
|
|
254
389
|
process.exit(1)
|
|
255
390
|
}
|
|
256
391
|
|
|
@@ -260,16 +395,20 @@ async function runSetup() {
|
|
|
260
395
|
const config = {
|
|
261
396
|
api_key: apiKey,
|
|
262
397
|
project_slug: projectSlug,
|
|
263
|
-
api_url: apiUrl,
|
|
264
398
|
created_at: new Date().toISOString()
|
|
265
399
|
}
|
|
266
400
|
|
|
267
401
|
saveConfig(config)
|
|
268
402
|
log('Configuration saved!', 'green')
|
|
269
403
|
|
|
270
|
-
|
|
404
|
+
return true
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Show setup complete message
|
|
408
|
+
function showSetupComplete() {
|
|
409
|
+
// Optional credentials
|
|
271
410
|
console.log()
|
|
272
|
-
log('
|
|
411
|
+
log('Optional: Local Credentials', 'bright')
|
|
273
412
|
log('For image generation and CMS publishing, create:', 'dim')
|
|
274
413
|
log(` ${CREDENTIALS_FILE}`, 'cyan')
|
|
275
414
|
console.log()
|
|
@@ -281,10 +420,6 @@ async function runSetup() {
|
|
|
281
420
|
"wordpress": {
|
|
282
421
|
"site_url": "https://your-site.com",
|
|
283
422
|
"secret_key": "FROM_PLUGIN_SETTINGS"
|
|
284
|
-
},
|
|
285
|
-
"ghost": {
|
|
286
|
-
"api_url": "https://your-ghost.com",
|
|
287
|
-
"admin_api_key": "YOUR_GHOST_ADMIN_KEY"
|
|
288
423
|
}
|
|
289
424
|
}`)
|
|
290
425
|
|
|
@@ -323,6 +458,35 @@ async function runSetup() {
|
|
|
323
458
|
log(' npx suparank clear - Clear session', 'dim')
|
|
324
459
|
}
|
|
325
460
|
|
|
461
|
+
async function runSetup() {
|
|
462
|
+
logHeader('Suparank Setup Wizard')
|
|
463
|
+
|
|
464
|
+
log('Welcome to Suparank!', 'cyan')
|
|
465
|
+
log('This wizard will connect your CLI to your Suparank account.', 'dim')
|
|
466
|
+
console.log()
|
|
467
|
+
|
|
468
|
+
// Offer setup method choice
|
|
469
|
+
log('Setup method:', 'bright')
|
|
470
|
+
log(' 1. Browser authorization (recommended)', 'dim')
|
|
471
|
+
log(' 2. Manual API key entry', 'dim')
|
|
472
|
+
console.log()
|
|
473
|
+
|
|
474
|
+
const choice = await prompt('Choice [1]: ')
|
|
475
|
+
|
|
476
|
+
console.log()
|
|
477
|
+
|
|
478
|
+
let success = false
|
|
479
|
+
if (choice === '2') {
|
|
480
|
+
success = await runManualSetup()
|
|
481
|
+
} else {
|
|
482
|
+
success = await runDeviceAuthSetup()
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (success) {
|
|
486
|
+
showSetupComplete()
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
326
490
|
async function runTest() {
|
|
327
491
|
logHeader('Testing Connection')
|
|
328
492
|
|
|
@@ -333,11 +497,11 @@ async function runTest() {
|
|
|
333
497
|
}
|
|
334
498
|
|
|
335
499
|
log(`Project: ${config.project_slug}`, 'dim')
|
|
336
|
-
log(`API URL: ${
|
|
500
|
+
log(`API URL: ${SUPARANK_API_URL}`, 'dim')
|
|
337
501
|
console.log()
|
|
338
502
|
|
|
339
503
|
log('Testing...', 'yellow')
|
|
340
|
-
const result = await testConnection(config.api_key, config.project_slug
|
|
504
|
+
const result = await testConnection(config.api_key, config.project_slug)
|
|
341
505
|
|
|
342
506
|
if (result.success) {
|
|
343
507
|
log(`Success! Connected to: ${result.project.name}`, 'green')
|
|
@@ -450,7 +614,7 @@ async function runMCP() {
|
|
|
450
614
|
stdio: ['inherit', 'inherit', 'inherit'],
|
|
451
615
|
env: {
|
|
452
616
|
...process.env,
|
|
453
|
-
SUPARANK_API_URL:
|
|
617
|
+
SUPARANK_API_URL: SUPARANK_API_URL
|
|
454
618
|
}
|
|
455
619
|
})
|
|
456
620
|
|