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.
Files changed (2) hide show
  1. package/bin/suparank.js +194 -30
  2. package/package.json +1 -1
package/bin/suparank.js CHANGED
@@ -178,10 +178,11 @@ function prompt(question) {
178
178
  })
179
179
  }
180
180
 
181
- async function testConnection(apiKey, projectSlug, apiUrl = null) {
181
+ const SUPARANK_API_URL = 'https://api.suparank.io'
182
+
183
+ async function testConnection(apiKey, projectSlug) {
182
184
  try {
183
- const url = apiUrl || process.env.SUPARANK_API_URL || 'http://localhost:3000'
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
- async function runSetup() {
206
- logHeader('Suparank Setup Wizard')
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, apiUrl)
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, project slug, and API URL.', 'dim')
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
- // Step 3: Optional credentials
404
+ return true
405
+ }
406
+
407
+ // Show setup complete message
408
+ function showSetupComplete() {
409
+ // Optional credentials
271
410
  console.log()
272
- log('Step 3: Local Credentials (optional)', 'bright')
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: ${config.api_url}`, 'dim')
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, config.api_url)
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: config.api_url
617
+ SUPARANK_API_URL: SUPARANK_API_URL
454
618
  }
455
619
  })
456
620
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suparank",
3
- "version": "1.3.4",
3
+ "version": "1.4.1",
4
4
  "description": "AI-powered SEO content creation MCP - generate and publish optimized blog posts with your AI assistant",
5
5
  "type": "module",
6
6
  "bin": {