suparank 1.0.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/LICENSE +21 -0
- package/README.md +224 -0
- package/bin/suparank.js +611 -0
- package/credentials.example.json +34 -0
- package/mcp-client.js +2446 -0
- package/package.json +54 -0
package/bin/suparank.js
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Suparank CLI - Interactive Setup and MCP Launcher
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx suparank - Run MCP (or setup if first time)
|
|
8
|
+
* npx suparank setup - Run setup wizard
|
|
9
|
+
* npx suparank credentials - Configure local credentials (WordPress, Ghost, etc.)
|
|
10
|
+
* npx suparank test - Test API connection
|
|
11
|
+
* npx suparank session - View current session state
|
|
12
|
+
* npx suparank clear - Clear session state
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as fs from 'fs'
|
|
16
|
+
import * as path from 'path'
|
|
17
|
+
import * as os from 'os'
|
|
18
|
+
import * as readline from 'readline'
|
|
19
|
+
import { spawn } from 'child_process'
|
|
20
|
+
|
|
21
|
+
const SUPARANK_DIR = path.join(os.homedir(), '.suparank')
|
|
22
|
+
const CONFIG_FILE = path.join(SUPARANK_DIR, 'config.json')
|
|
23
|
+
const CREDENTIALS_FILE = path.join(SUPARANK_DIR, 'credentials.json')
|
|
24
|
+
const SESSION_FILE = path.join(SUPARANK_DIR, 'session.json')
|
|
25
|
+
|
|
26
|
+
// Production API URL
|
|
27
|
+
const DEFAULT_API_URL = 'https://api.suparank.io'
|
|
28
|
+
|
|
29
|
+
// Colors for terminal output
|
|
30
|
+
const colors = {
|
|
31
|
+
reset: '\x1b[0m',
|
|
32
|
+
bright: '\x1b[1m',
|
|
33
|
+
dim: '\x1b[2m',
|
|
34
|
+
green: '\x1b[32m',
|
|
35
|
+
yellow: '\x1b[33m',
|
|
36
|
+
blue: '\x1b[34m',
|
|
37
|
+
red: '\x1b[31m',
|
|
38
|
+
cyan: '\x1b[36m',
|
|
39
|
+
magenta: '\x1b[35m'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function log(message, color = 'reset') {
|
|
43
|
+
console.log(`${colors[color]}${message}${colors.reset}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function logHeader(message) {
|
|
47
|
+
console.log()
|
|
48
|
+
log(`${'='.repeat(50)}`, 'cyan')
|
|
49
|
+
log(` ${message}`, 'bright')
|
|
50
|
+
log(`${'='.repeat(50)}`, 'cyan')
|
|
51
|
+
console.log()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function logStep(step, total, message) {
|
|
55
|
+
log(`[${step}/${total}] ${message}`, 'yellow')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ensureDir() {
|
|
59
|
+
if (!fs.existsSync(SUPARANK_DIR)) {
|
|
60
|
+
fs.mkdirSync(SUPARANK_DIR, { recursive: true })
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function loadConfig() {
|
|
65
|
+
try {
|
|
66
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
67
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'))
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
// Ignore errors
|
|
71
|
+
}
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function saveConfig(config) {
|
|
76
|
+
ensureDir()
|
|
77
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function loadCredentials() {
|
|
81
|
+
try {
|
|
82
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
83
|
+
return JSON.parse(fs.readFileSync(CREDENTIALS_FILE, 'utf-8'))
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Ignore errors
|
|
87
|
+
}
|
|
88
|
+
return {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function saveCredentials(credentials) {
|
|
92
|
+
ensureDir()
|
|
93
|
+
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2))
|
|
94
|
+
// Set restrictive permissions
|
|
95
|
+
fs.chmodSync(CREDENTIALS_FILE, 0o600)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function prompt(question) {
|
|
99
|
+
const rl = readline.createInterface({
|
|
100
|
+
input: process.stdin,
|
|
101
|
+
output: process.stdout
|
|
102
|
+
})
|
|
103
|
+
return new Promise(resolve => {
|
|
104
|
+
rl.question(question, answer => {
|
|
105
|
+
rl.close()
|
|
106
|
+
resolve(answer.trim())
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function promptPassword(question) {
|
|
112
|
+
return new Promise(resolve => {
|
|
113
|
+
const rl = readline.createInterface({
|
|
114
|
+
input: process.stdin,
|
|
115
|
+
output: process.stdout
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Hide input for passwords
|
|
119
|
+
process.stdout.write(question)
|
|
120
|
+
let password = ''
|
|
121
|
+
|
|
122
|
+
process.stdin.setRawMode(true)
|
|
123
|
+
process.stdin.resume()
|
|
124
|
+
process.stdin.setEncoding('utf8')
|
|
125
|
+
|
|
126
|
+
const onData = (char) => {
|
|
127
|
+
if (char === '\n' || char === '\r') {
|
|
128
|
+
process.stdin.setRawMode(false)
|
|
129
|
+
process.stdin.removeListener('data', onData)
|
|
130
|
+
rl.close()
|
|
131
|
+
console.log()
|
|
132
|
+
resolve(password)
|
|
133
|
+
} else if (char === '\u0003') {
|
|
134
|
+
process.exit()
|
|
135
|
+
} else if (char === '\u007F') {
|
|
136
|
+
password = password.slice(0, -1)
|
|
137
|
+
process.stdout.clearLine(0)
|
|
138
|
+
process.stdout.cursorTo(0)
|
|
139
|
+
process.stdout.write(question + '*'.repeat(password.length))
|
|
140
|
+
} else {
|
|
141
|
+
password += char
|
|
142
|
+
process.stdout.write('*')
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
process.stdin.on('data', onData)
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function testConnection(apiKey, projectSlug, apiUrl = null) {
|
|
151
|
+
try {
|
|
152
|
+
const url = apiUrl || DEFAULT_API_URL
|
|
153
|
+
const response = await fetch(`${url}/projects/${projectSlug}`, {
|
|
154
|
+
headers: {
|
|
155
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
156
|
+
'Content-Type': 'application/json'
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
if (response.ok) {
|
|
161
|
+
const data = await response.json()
|
|
162
|
+
const project = data.project || data
|
|
163
|
+
return { success: true, project }
|
|
164
|
+
} else {
|
|
165
|
+
const error = await response.text()
|
|
166
|
+
return { success: false, error: `HTTP ${response.status}: ${error}` }
|
|
167
|
+
}
|
|
168
|
+
} catch (e) {
|
|
169
|
+
return { success: false, error: e.message }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function runSetup() {
|
|
174
|
+
logHeader('Suparank Setup Wizard')
|
|
175
|
+
|
|
176
|
+
log('Welcome to Suparank! ', 'green')
|
|
177
|
+
log('AI-powered SEO content creation for your blog.', 'dim')
|
|
178
|
+
console.log()
|
|
179
|
+
log('This wizard will help you:', 'cyan')
|
|
180
|
+
log(' 1. Connect to your Suparank account', 'dim')
|
|
181
|
+
log(' 2. Configure your project', 'dim')
|
|
182
|
+
log(' 3. Set up local integrations (optional)', 'dim')
|
|
183
|
+
console.log()
|
|
184
|
+
|
|
185
|
+
// Step 1: API Key
|
|
186
|
+
logStep(1, 3, 'Suparank Account')
|
|
187
|
+
console.log()
|
|
188
|
+
log('Get your API key from:', 'dim')
|
|
189
|
+
log(' https://suparank.io/dashboard/settings/api-keys', 'cyan')
|
|
190
|
+
console.log()
|
|
191
|
+
|
|
192
|
+
const apiKey = await prompt('Enter your API key: ')
|
|
193
|
+
if (!apiKey) {
|
|
194
|
+
log('API key is required. Exiting.', 'red')
|
|
195
|
+
process.exit(1)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Validate API key format
|
|
199
|
+
if (!apiKey.startsWith('sk_live_') && !apiKey.startsWith('sk_test_')) {
|
|
200
|
+
log('Invalid API key format. Keys must start with sk_live_ or sk_test_', 'red')
|
|
201
|
+
process.exit(1)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Step 2: Project slug
|
|
205
|
+
console.log()
|
|
206
|
+
logStep(2, 3, 'Project Selection')
|
|
207
|
+
console.log()
|
|
208
|
+
log('Enter your project slug (from your dashboard URL)', 'dim')
|
|
209
|
+
log('Example: my-blog-abc123', 'dim')
|
|
210
|
+
console.log()
|
|
211
|
+
|
|
212
|
+
const projectSlug = await prompt('Project slug: ')
|
|
213
|
+
if (!projectSlug) {
|
|
214
|
+
log('Project slug is required. Exiting.', 'red')
|
|
215
|
+
process.exit(1)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Test connection
|
|
219
|
+
console.log()
|
|
220
|
+
log('Testing connection...', 'yellow')
|
|
221
|
+
|
|
222
|
+
const result = await testConnection(apiKey, projectSlug, DEFAULT_API_URL)
|
|
223
|
+
|
|
224
|
+
if (!result.success) {
|
|
225
|
+
log(`Connection failed: ${result.error}`, 'red')
|
|
226
|
+
log('Please check your API key and project slug.', 'dim')
|
|
227
|
+
process.exit(1)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
log(`Connected to: ${result.project.name}`, 'green')
|
|
231
|
+
|
|
232
|
+
// Save config
|
|
233
|
+
const config = {
|
|
234
|
+
api_key: apiKey,
|
|
235
|
+
project_slug: projectSlug,
|
|
236
|
+
api_url: DEFAULT_API_URL,
|
|
237
|
+
created_at: new Date().toISOString()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
saveConfig(config)
|
|
241
|
+
log('Configuration saved!', 'green')
|
|
242
|
+
|
|
243
|
+
// Step 3: Local credentials (optional)
|
|
244
|
+
console.log()
|
|
245
|
+
logStep(3, 3, 'Local Integrations (Optional)')
|
|
246
|
+
console.log()
|
|
247
|
+
log('Set up local integrations for:', 'dim')
|
|
248
|
+
log(' - Image generation (fal.ai, Gemini, wiro.ai)', 'dim')
|
|
249
|
+
log(' - WordPress publishing', 'dim')
|
|
250
|
+
log(' - Ghost CMS publishing', 'dim')
|
|
251
|
+
log(' - Webhooks (Make, n8n, Zapier, Slack)', 'dim')
|
|
252
|
+
console.log()
|
|
253
|
+
|
|
254
|
+
const setupCreds = await prompt('Configure integrations now? (y/N): ')
|
|
255
|
+
|
|
256
|
+
if (setupCreds.toLowerCase() === 'y') {
|
|
257
|
+
await runCredentialsSetup()
|
|
258
|
+
} else {
|
|
259
|
+
log('You can configure integrations later with: npx suparank credentials', 'dim')
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Final instructions
|
|
263
|
+
logHeader('Setup Complete!')
|
|
264
|
+
|
|
265
|
+
log('Add Suparank to your AI client:', 'bright')
|
|
266
|
+
console.log()
|
|
267
|
+
|
|
268
|
+
log('For Claude Desktop:', 'cyan')
|
|
269
|
+
log('Edit ~/.config/claude/claude_desktop_config.json:', 'dim')
|
|
270
|
+
console.log(`{
|
|
271
|
+
"mcpServers": {
|
|
272
|
+
"suparank": {
|
|
273
|
+
"command": "npx",
|
|
274
|
+
"args": ["suparank"]
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}`)
|
|
278
|
+
|
|
279
|
+
console.log()
|
|
280
|
+
log('For Cursor:', 'cyan')
|
|
281
|
+
log('Add to your MCP settings:', 'dim')
|
|
282
|
+
console.log(`{
|
|
283
|
+
"mcpServers": {
|
|
284
|
+
"suparank": {
|
|
285
|
+
"command": "npx",
|
|
286
|
+
"args": ["suparank"]
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}`)
|
|
290
|
+
|
|
291
|
+
console.log()
|
|
292
|
+
log('Commands:', 'bright')
|
|
293
|
+
log(' npx suparank Run MCP server', 'dim')
|
|
294
|
+
log(' npx suparank setup Re-run setup', 'dim')
|
|
295
|
+
log(' npx suparank credentials Configure integrations', 'dim')
|
|
296
|
+
log(' npx suparank test Test connection', 'dim')
|
|
297
|
+
log(' npx suparank session View session state', 'dim')
|
|
298
|
+
log(' npx suparank clear Clear session', 'dim')
|
|
299
|
+
console.log()
|
|
300
|
+
log('Documentation: https://suparank.io/docs', 'cyan')
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function runCredentialsSetup() {
|
|
304
|
+
logHeader('Configure Integrations')
|
|
305
|
+
|
|
306
|
+
const credentials = loadCredentials()
|
|
307
|
+
|
|
308
|
+
// Image Generation
|
|
309
|
+
log('Image Generation', 'bright')
|
|
310
|
+
log('Generate AI images for your blog posts', 'dim')
|
|
311
|
+
console.log()
|
|
312
|
+
log('Providers:', 'cyan')
|
|
313
|
+
log(' 1. fal.ai (recommended) - Fast, high quality', 'dim')
|
|
314
|
+
log(' 2. wiro.ai - Google Imagen via API', 'dim')
|
|
315
|
+
log(' 3. Gemini - Google AI directly', 'dim')
|
|
316
|
+
log(' 4. Skip', 'dim')
|
|
317
|
+
console.log()
|
|
318
|
+
|
|
319
|
+
const imageChoice = await prompt('Choose provider (1-4): ')
|
|
320
|
+
|
|
321
|
+
if (imageChoice === '1') {
|
|
322
|
+
const apiKey = await prompt('fal.ai API key: ')
|
|
323
|
+
if (apiKey) {
|
|
324
|
+
credentials.image_provider = 'fal'
|
|
325
|
+
credentials.fal = { api_key: apiKey }
|
|
326
|
+
log('fal.ai configured!', 'green')
|
|
327
|
+
}
|
|
328
|
+
} else if (imageChoice === '2') {
|
|
329
|
+
const apiKey = await prompt('wiro.ai API key: ')
|
|
330
|
+
const apiSecret = await prompt('wiro.ai API secret: ')
|
|
331
|
+
if (apiKey && apiSecret) {
|
|
332
|
+
credentials.image_provider = 'wiro'
|
|
333
|
+
credentials.wiro = {
|
|
334
|
+
api_key: apiKey,
|
|
335
|
+
api_secret: apiSecret,
|
|
336
|
+
model: 'google/nano-banana-pro'
|
|
337
|
+
}
|
|
338
|
+
log('wiro.ai configured!', 'green')
|
|
339
|
+
}
|
|
340
|
+
} else if (imageChoice === '3') {
|
|
341
|
+
const apiKey = await prompt('Google AI API key: ')
|
|
342
|
+
if (apiKey) {
|
|
343
|
+
credentials.image_provider = 'gemini'
|
|
344
|
+
credentials.gemini = { api_key: apiKey }
|
|
345
|
+
log('Gemini configured!', 'green')
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// WordPress
|
|
350
|
+
console.log()
|
|
351
|
+
log('WordPress Publishing', 'bright')
|
|
352
|
+
log('Publish directly to your WordPress site', 'dim')
|
|
353
|
+
console.log()
|
|
354
|
+
|
|
355
|
+
const setupWP = await prompt('Configure WordPress? (y/N): ')
|
|
356
|
+
if (setupWP.toLowerCase() === 'y') {
|
|
357
|
+
log('Install the Suparank Connector plugin from:', 'dim')
|
|
358
|
+
log(' https://suparank.io/wordpress-plugin', 'cyan')
|
|
359
|
+
console.log()
|
|
360
|
+
|
|
361
|
+
const siteUrl = await prompt('WordPress site URL (https://your-site.com): ')
|
|
362
|
+
const secretKey = await prompt('Plugin secret key (from plugin settings): ')
|
|
363
|
+
|
|
364
|
+
if (siteUrl && secretKey) {
|
|
365
|
+
credentials.wordpress = {
|
|
366
|
+
site_url: siteUrl.replace(/\/$/, ''),
|
|
367
|
+
secret_key: secretKey
|
|
368
|
+
}
|
|
369
|
+
log('WordPress configured!', 'green')
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Ghost
|
|
374
|
+
console.log()
|
|
375
|
+
log('Ghost CMS Publishing', 'bright')
|
|
376
|
+
log('Publish directly to your Ghost blog', 'dim')
|
|
377
|
+
console.log()
|
|
378
|
+
|
|
379
|
+
const setupGhost = await prompt('Configure Ghost? (y/N): ')
|
|
380
|
+
if (setupGhost.toLowerCase() === 'y') {
|
|
381
|
+
const apiUrl = await prompt('Ghost site URL (https://your-ghost.com): ')
|
|
382
|
+
const adminKey = await prompt('Admin API key (from Ghost settings): ')
|
|
383
|
+
|
|
384
|
+
if (apiUrl && adminKey) {
|
|
385
|
+
credentials.ghost = {
|
|
386
|
+
api_url: apiUrl.replace(/\/$/, ''),
|
|
387
|
+
admin_api_key: adminKey
|
|
388
|
+
}
|
|
389
|
+
log('Ghost configured!', 'green')
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Webhooks
|
|
394
|
+
console.log()
|
|
395
|
+
log('Webhooks (Optional)', 'bright')
|
|
396
|
+
log('Send notifications to Make, n8n, Zapier, or Slack', 'dim')
|
|
397
|
+
console.log()
|
|
398
|
+
|
|
399
|
+
const setupWebhooks = await prompt('Configure webhooks? (y/N): ')
|
|
400
|
+
if (setupWebhooks.toLowerCase() === 'y') {
|
|
401
|
+
credentials.webhooks = credentials.webhooks || {}
|
|
402
|
+
|
|
403
|
+
const slackUrl = await prompt('Slack webhook URL (or Enter to skip): ')
|
|
404
|
+
if (slackUrl) credentials.webhooks.slack_url = slackUrl
|
|
405
|
+
|
|
406
|
+
const makeUrl = await prompt('Make.com webhook URL (or Enter to skip): ')
|
|
407
|
+
if (makeUrl) credentials.webhooks.make_url = makeUrl
|
|
408
|
+
|
|
409
|
+
const zapierUrl = await prompt('Zapier webhook URL (or Enter to skip): ')
|
|
410
|
+
if (zapierUrl) credentials.webhooks.zapier_url = zapierUrl
|
|
411
|
+
|
|
412
|
+
log('Webhooks configured!', 'green')
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Save credentials
|
|
416
|
+
saveCredentials(credentials)
|
|
417
|
+
console.log()
|
|
418
|
+
log('Credentials saved to ~/.suparank/credentials.json', 'green')
|
|
419
|
+
log('File permissions set to owner-only (600)', 'dim')
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function runTest() {
|
|
423
|
+
logHeader('Testing Connection')
|
|
424
|
+
|
|
425
|
+
const config = loadConfig()
|
|
426
|
+
if (!config) {
|
|
427
|
+
log('No configuration found. Run: npx suparank setup', 'red')
|
|
428
|
+
process.exit(1)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
log(`Project: ${config.project_slug}`, 'dim')
|
|
432
|
+
log(`API URL: ${config.api_url}`, 'dim')
|
|
433
|
+
console.log()
|
|
434
|
+
|
|
435
|
+
log('Testing...', 'yellow')
|
|
436
|
+
const result = await testConnection(config.api_key, config.project_slug, config.api_url)
|
|
437
|
+
|
|
438
|
+
if (result.success) {
|
|
439
|
+
log(`Success! Connected to: ${result.project.name}`, 'green')
|
|
440
|
+
console.log()
|
|
441
|
+
|
|
442
|
+
// Check credentials
|
|
443
|
+
const creds = loadCredentials()
|
|
444
|
+
const configured = []
|
|
445
|
+
if (creds.wordpress?.secret_key) configured.push('WordPress')
|
|
446
|
+
if (creds.ghost?.admin_api_key) configured.push('Ghost')
|
|
447
|
+
if (creds[creds.image_provider]?.api_key) configured.push(`Images (${creds.image_provider})`)
|
|
448
|
+
if (creds.webhooks && Object.values(creds.webhooks).some(Boolean)) configured.push('Webhooks')
|
|
449
|
+
|
|
450
|
+
if (configured.length > 0) {
|
|
451
|
+
log('Local integrations:', 'cyan')
|
|
452
|
+
configured.forEach(c => log(` - ${c}`, 'green'))
|
|
453
|
+
} else {
|
|
454
|
+
log('No local integrations configured', 'dim')
|
|
455
|
+
log('Run: npx suparank credentials', 'dim')
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
log(`Connection failed: ${result.error}`, 'red')
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function viewSession() {
|
|
463
|
+
logHeader('Session State')
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
if (fs.existsSync(SESSION_FILE)) {
|
|
467
|
+
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'))
|
|
468
|
+
|
|
469
|
+
if (session.title) {
|
|
470
|
+
log(`Title: ${session.title}`, 'green')
|
|
471
|
+
log(`Words: ${session.article?.split(/\s+/).length || 0}`, 'dim')
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (session.imageUrl) {
|
|
475
|
+
log(`Cover Image: ${session.imageUrl.substring(0, 60)}...`, 'cyan')
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (session.inlineImages?.length > 0) {
|
|
479
|
+
log(`Inline Images: ${session.inlineImages.length}`, 'cyan')
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (session.currentWorkflow) {
|
|
483
|
+
log(`Workflow: ${session.currentWorkflow.workflow_id}`, 'yellow')
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
log(`Saved: ${session.savedAt}`, 'dim')
|
|
487
|
+
} else {
|
|
488
|
+
log('No active session', 'dim')
|
|
489
|
+
}
|
|
490
|
+
} catch (e) {
|
|
491
|
+
log(`Error reading session: ${e.message}`, 'red')
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function clearSession() {
|
|
496
|
+
logHeader('Clear Session')
|
|
497
|
+
|
|
498
|
+
try {
|
|
499
|
+
if (fs.existsSync(SESSION_FILE)) {
|
|
500
|
+
fs.unlinkSync(SESSION_FILE)
|
|
501
|
+
log('Session cleared!', 'green')
|
|
502
|
+
} else {
|
|
503
|
+
log('No session to clear', 'dim')
|
|
504
|
+
}
|
|
505
|
+
} catch (e) {
|
|
506
|
+
log(`Error clearing session: ${e.message}`, 'red')
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function showVersion() {
|
|
511
|
+
const packageJson = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'))
|
|
512
|
+
log(`Suparank MCP v${packageJson.version}`, 'cyan')
|
|
513
|
+
log('https://suparank.io', 'dim')
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function runMCP() {
|
|
517
|
+
const config = loadConfig()
|
|
518
|
+
|
|
519
|
+
if (!config) {
|
|
520
|
+
log('No configuration found. Running setup...', 'yellow')
|
|
521
|
+
console.log()
|
|
522
|
+
runSetup()
|
|
523
|
+
return
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Find the MCP client script
|
|
527
|
+
const mcpClientPaths = [
|
|
528
|
+
path.join(import.meta.dirname, '..', 'mcp-client.js'),
|
|
529
|
+
path.join(process.cwd(), 'mcp-client.js')
|
|
530
|
+
]
|
|
531
|
+
|
|
532
|
+
let mcpClientPath = null
|
|
533
|
+
for (const p of mcpClientPaths) {
|
|
534
|
+
if (fs.existsSync(p)) {
|
|
535
|
+
mcpClientPath = p
|
|
536
|
+
break
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (!mcpClientPath) {
|
|
541
|
+
log('Error: mcp-client.js not found', 'red')
|
|
542
|
+
process.exit(1)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Launch MCP client with config
|
|
546
|
+
const child = spawn('node', [mcpClientPath, config.project_slug, config.api_key], {
|
|
547
|
+
stdio: 'inherit',
|
|
548
|
+
env: {
|
|
549
|
+
...process.env,
|
|
550
|
+
SUPARANK_API_URL: config.api_url
|
|
551
|
+
}
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
child.on('error', (err) => {
|
|
555
|
+
console.error('Failed to start MCP client:', err.message)
|
|
556
|
+
process.exit(1)
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
child.on('exit', (code) => {
|
|
560
|
+
process.exit(code || 0)
|
|
561
|
+
})
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Main entry point
|
|
565
|
+
const command = process.argv[2]
|
|
566
|
+
|
|
567
|
+
switch (command) {
|
|
568
|
+
case 'setup':
|
|
569
|
+
runSetup()
|
|
570
|
+
break
|
|
571
|
+
case 'credentials':
|
|
572
|
+
case 'creds':
|
|
573
|
+
runCredentialsSetup()
|
|
574
|
+
break
|
|
575
|
+
case 'test':
|
|
576
|
+
runTest()
|
|
577
|
+
break
|
|
578
|
+
case 'session':
|
|
579
|
+
viewSession()
|
|
580
|
+
break
|
|
581
|
+
case 'clear':
|
|
582
|
+
clearSession()
|
|
583
|
+
break
|
|
584
|
+
case 'version':
|
|
585
|
+
case '-v':
|
|
586
|
+
case '--version':
|
|
587
|
+
showVersion()
|
|
588
|
+
break
|
|
589
|
+
case 'help':
|
|
590
|
+
case '--help':
|
|
591
|
+
case '-h':
|
|
592
|
+
logHeader('Suparank CLI')
|
|
593
|
+
log('AI-powered SEO content creation MCP', 'dim')
|
|
594
|
+
console.log()
|
|
595
|
+
log('Usage: npx suparank [command]', 'cyan')
|
|
596
|
+
console.log()
|
|
597
|
+
log('Commands:', 'bright')
|
|
598
|
+
log(' (none) Run MCP server (default)', 'dim')
|
|
599
|
+
log(' setup Run setup wizard', 'dim')
|
|
600
|
+
log(' credentials Configure local integrations', 'dim')
|
|
601
|
+
log(' test Test API connection', 'dim')
|
|
602
|
+
log(' session View current session state', 'dim')
|
|
603
|
+
log(' clear Clear session state', 'dim')
|
|
604
|
+
log(' version Show version', 'dim')
|
|
605
|
+
log(' help Show this help message', 'dim')
|
|
606
|
+
console.log()
|
|
607
|
+
log('Documentation: https://suparank.io/docs', 'cyan')
|
|
608
|
+
break
|
|
609
|
+
default:
|
|
610
|
+
runMCP()
|
|
611
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"image_provider": "fal",
|
|
3
|
+
|
|
4
|
+
"fal": {
|
|
5
|
+
"api_key": "your-fal-api-key"
|
|
6
|
+
},
|
|
7
|
+
|
|
8
|
+
"wiro": {
|
|
9
|
+
"api_key": "your-wiro-api-key",
|
|
10
|
+
"api_secret": "your-wiro-api-secret",
|
|
11
|
+
"model": "google/nano-banana-pro"
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
"gemini": {
|
|
15
|
+
"api_key": "your-google-ai-api-key"
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
"wordpress": {
|
|
19
|
+
"site_url": "https://your-site.com",
|
|
20
|
+
"secret_key": "from-suparank-connector-plugin"
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
"ghost": {
|
|
24
|
+
"api_url": "https://your-ghost-site.com",
|
|
25
|
+
"admin_api_key": "your-admin-api-key"
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
"webhooks": {
|
|
29
|
+
"slack_url": "https://hooks.slack.com/services/xxx",
|
|
30
|
+
"make_url": "https://hook.make.com/xxx",
|
|
31
|
+
"n8n_url": "https://your-n8n.com/webhook/xxx",
|
|
32
|
+
"zapier_url": "https://hooks.zapier.com/xxx"
|
|
33
|
+
}
|
|
34
|
+
}
|