suparank 1.2.6 → 1.2.7
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 +85 -536
- package/credentials.example.json +36 -18
- package/mcp-client/handlers/action.js +33 -0
- package/mcp-client/handlers/backend.js +43 -0
- package/mcp-client/handlers/index.js +9 -0
- package/mcp-client/handlers/orchestrator.js +850 -0
- package/mcp-client/index.js +22 -11
- package/mcp-client/publishers/ghost.js +105 -0
- package/mcp-client/publishers/image.js +306 -0
- package/mcp-client/publishers/index.js +20 -0
- package/mcp-client/publishers/webhook.js +76 -0
- package/mcp-client/publishers/wordpress.js +220 -0
- package/mcp-client/server.js +220 -0
- package/mcp-client/services/credentials.js +22 -0
- package/mcp-client/services/index.js +1 -0
- package/mcp-client/services/project.js +40 -0
- package/mcp-client/tools/definitions.js +679 -0
- package/mcp-client/tools/discovery.js +132 -0
- package/mcp-client/tools/index.js +22 -0
- package/mcp-client/utils/content.js +126 -0
- package/mcp-client/utils/formatting.js +71 -0
- package/mcp-client/utils/index.js +2 -0
- package/mcp-client/workflow/index.js +10 -0
- package/mcp-client/workflow/planner.js +513 -0
- package/package.json +7 -19
- package/mcp-client.js +0 -3693
package/bin/suparank.js
CHANGED
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
* Usage:
|
|
7
7
|
* npx suparank - Run MCP (or setup if first time)
|
|
8
8
|
* npx suparank setup - Run setup wizard
|
|
9
|
-
* npx suparank credentials - Configure local credentials (WordPress, Ghost, etc.)
|
|
10
9
|
* npx suparank test - Test API connection
|
|
11
10
|
* npx suparank session - View current session state
|
|
12
11
|
* npx suparank clear - Clear session state
|
|
13
|
-
* npx suparank update - Check for updates and install latest version
|
|
14
12
|
*/
|
|
15
13
|
|
|
16
14
|
import * as fs from 'fs'
|
|
@@ -23,11 +21,6 @@ const SUPARANK_DIR = path.join(os.homedir(), '.suparank')
|
|
|
23
21
|
const CONFIG_FILE = path.join(SUPARANK_DIR, 'config.json')
|
|
24
22
|
const CREDENTIALS_FILE = path.join(SUPARANK_DIR, 'credentials.json')
|
|
25
23
|
const SESSION_FILE = path.join(SUPARANK_DIR, 'session.json')
|
|
26
|
-
const CONTENT_DIR = path.join(SUPARANK_DIR, 'content')
|
|
27
|
-
const STATS_FILE = path.join(SUPARANK_DIR, 'stats.json')
|
|
28
|
-
|
|
29
|
-
// Production API URL
|
|
30
|
-
const DEFAULT_API_URL = 'https://api.suparank.io'
|
|
31
24
|
|
|
32
25
|
// Colors for terminal output
|
|
33
26
|
const colors = {
|
|
@@ -38,8 +31,7 @@ const colors = {
|
|
|
38
31
|
yellow: '\x1b[33m',
|
|
39
32
|
blue: '\x1b[34m',
|
|
40
33
|
red: '\x1b[31m',
|
|
41
|
-
cyan: '\x1b[36m'
|
|
42
|
-
magenta: '\x1b[35m'
|
|
34
|
+
cyan: '\x1b[36m'
|
|
43
35
|
}
|
|
44
36
|
|
|
45
37
|
function log(message, color = 'reset') {
|
|
@@ -48,16 +40,10 @@ function log(message, color = 'reset') {
|
|
|
48
40
|
|
|
49
41
|
function logHeader(message) {
|
|
50
42
|
console.log()
|
|
51
|
-
log(
|
|
52
|
-
log(` ${message}`, 'bright')
|
|
53
|
-
log(`${'='.repeat(50)}`, 'cyan')
|
|
43
|
+
log(`=== ${message} ===`, 'bright')
|
|
54
44
|
console.log()
|
|
55
45
|
}
|
|
56
46
|
|
|
57
|
-
function logStep(step, total, message) {
|
|
58
|
-
log(`[${step}/${total}] ${message}`, 'yellow')
|
|
59
|
-
}
|
|
60
|
-
|
|
61
47
|
function ensureDir() {
|
|
62
48
|
if (!fs.existsSync(SUPARANK_DIR)) {
|
|
63
49
|
fs.mkdirSync(SUPARANK_DIR, { recursive: true })
|
|
@@ -85,17 +71,20 @@ function loadCredentials() {
|
|
|
85
71
|
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
86
72
|
return JSON.parse(fs.readFileSync(CREDENTIALS_FILE, 'utf-8'))
|
|
87
73
|
}
|
|
74
|
+
// Try legacy .env.superwriter
|
|
75
|
+
const legacyPaths = [
|
|
76
|
+
path.join(os.homedir(), '.env.superwriter'),
|
|
77
|
+
path.join(process.cwd(), '.env.superwriter')
|
|
78
|
+
]
|
|
79
|
+
for (const legacyPath of legacyPaths) {
|
|
80
|
+
if (fs.existsSync(legacyPath)) {
|
|
81
|
+
return JSON.parse(fs.readFileSync(legacyPath, 'utf-8'))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
88
84
|
} catch (e) {
|
|
89
85
|
// Ignore errors
|
|
90
86
|
}
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function saveCredentials(credentials) {
|
|
95
|
-
ensureDir()
|
|
96
|
-
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2))
|
|
97
|
-
// Set restrictive permissions
|
|
98
|
-
fs.chmodSync(CREDENTIALS_FILE, 0o600)
|
|
87
|
+
return null
|
|
99
88
|
}
|
|
100
89
|
|
|
101
90
|
function prompt(question) {
|
|
@@ -111,48 +100,9 @@ function prompt(question) {
|
|
|
111
100
|
})
|
|
112
101
|
}
|
|
113
102
|
|
|
114
|
-
function promptPassword(question) {
|
|
115
|
-
return new Promise(resolve => {
|
|
116
|
-
const rl = readline.createInterface({
|
|
117
|
-
input: process.stdin,
|
|
118
|
-
output: process.stdout
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
// Hide input for passwords
|
|
122
|
-
process.stdout.write(question)
|
|
123
|
-
let password = ''
|
|
124
|
-
|
|
125
|
-
process.stdin.setRawMode(true)
|
|
126
|
-
process.stdin.resume()
|
|
127
|
-
process.stdin.setEncoding('utf8')
|
|
128
|
-
|
|
129
|
-
const onData = (char) => {
|
|
130
|
-
if (char === '\n' || char === '\r') {
|
|
131
|
-
process.stdin.setRawMode(false)
|
|
132
|
-
process.stdin.removeListener('data', onData)
|
|
133
|
-
rl.close()
|
|
134
|
-
console.log()
|
|
135
|
-
resolve(password)
|
|
136
|
-
} else if (char === '\u0003') {
|
|
137
|
-
process.exit()
|
|
138
|
-
} else if (char === '\u007F') {
|
|
139
|
-
password = password.slice(0, -1)
|
|
140
|
-
process.stdout.clearLine(0)
|
|
141
|
-
process.stdout.cursorTo(0)
|
|
142
|
-
process.stdout.write(question + '*'.repeat(password.length))
|
|
143
|
-
} else {
|
|
144
|
-
password += char
|
|
145
|
-
process.stdout.write('*')
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
process.stdin.on('data', onData)
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
|
|
153
103
|
async function testConnection(apiKey, projectSlug, apiUrl = null) {
|
|
154
104
|
try {
|
|
155
|
-
const url = apiUrl ||
|
|
105
|
+
const url = apiUrl || process.env.SUPARANK_API_URL || 'http://localhost:3000'
|
|
156
106
|
const response = await fetch(`${url}/projects/${projectSlug}`, {
|
|
157
107
|
headers: {
|
|
158
108
|
'Authorization': `Bearer ${apiKey}`,
|
|
@@ -162,6 +112,7 @@ async function testConnection(apiKey, projectSlug, apiUrl = null) {
|
|
|
162
112
|
|
|
163
113
|
if (response.ok) {
|
|
164
114
|
const data = await response.json()
|
|
115
|
+
// API returns { project: {...} }
|
|
165
116
|
const project = data.project || data
|
|
166
117
|
return { success: true, project }
|
|
167
118
|
} else {
|
|
@@ -176,20 +127,13 @@ async function testConnection(apiKey, projectSlug, apiUrl = null) {
|
|
|
176
127
|
async function runSetup() {
|
|
177
128
|
logHeader('Suparank Setup Wizard')
|
|
178
129
|
|
|
179
|
-
log('Welcome to Suparank!
|
|
180
|
-
log('
|
|
181
|
-
console.log()
|
|
182
|
-
log('This wizard will help you:', 'cyan')
|
|
183
|
-
log(' 1. Connect to your Suparank account', 'dim')
|
|
184
|
-
log(' 2. Configure your project', 'dim')
|
|
185
|
-
log(' 3. Set up local integrations (optional)', 'dim')
|
|
130
|
+
log('Welcome to Suparank!', 'cyan')
|
|
131
|
+
log('This wizard will help you configure your MCP client.', 'dim')
|
|
186
132
|
console.log()
|
|
187
133
|
|
|
188
|
-
// Step 1: API
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
log('Get your API key from:', 'dim')
|
|
192
|
-
log(' https://suparank.io/dashboard/settings/api-keys', 'cyan')
|
|
134
|
+
// Step 1: Get API key
|
|
135
|
+
log('Step 1: API Key', 'bright')
|
|
136
|
+
log('Get your API key from: https://suparank.io/dashboard/settings/api-keys', 'dim')
|
|
193
137
|
console.log()
|
|
194
138
|
|
|
195
139
|
const apiKey = await prompt('Enter your API key: ')
|
|
@@ -198,78 +142,80 @@ async function runSetup() {
|
|
|
198
142
|
process.exit(1)
|
|
199
143
|
}
|
|
200
144
|
|
|
201
|
-
//
|
|
202
|
-
if (!apiKey.startsWith('sk_live_') && !apiKey.startsWith('sk_test_')) {
|
|
203
|
-
log('Invalid API key format. Keys must start with sk_live_ or sk_test_', 'red')
|
|
204
|
-
process.exit(1)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Step 2: Project slug
|
|
208
|
-
console.log()
|
|
209
|
-
logStep(2, 3, 'Project Selection')
|
|
145
|
+
// Step 2: Get project slug
|
|
210
146
|
console.log()
|
|
147
|
+
log('Step 2: Project', 'bright')
|
|
211
148
|
log('Enter your project slug (from your dashboard URL)', 'dim')
|
|
212
|
-
log('Example: my-blog-abc123', 'dim')
|
|
213
149
|
console.log()
|
|
214
150
|
|
|
215
|
-
const projectSlug = await prompt('
|
|
151
|
+
const projectSlug = await prompt('Enter project slug: ')
|
|
216
152
|
if (!projectSlug) {
|
|
217
153
|
log('Project slug is required. Exiting.', 'red')
|
|
218
154
|
process.exit(1)
|
|
219
155
|
}
|
|
220
156
|
|
|
157
|
+
// Step 3: API URL (optional)
|
|
158
|
+
console.log()
|
|
159
|
+
log('Step 3: API URL (optional)', 'bright')
|
|
160
|
+
log('Press Enter for default, or enter custom URL for self-hosted/development', 'dim')
|
|
161
|
+
const defaultUrl = process.env.SUPARANK_API_URL || 'http://localhost:3000'
|
|
162
|
+
console.log()
|
|
163
|
+
|
|
164
|
+
const apiUrlInput = await prompt(`API URL [${defaultUrl}]: `)
|
|
165
|
+
const apiUrl = apiUrlInput || defaultUrl
|
|
166
|
+
|
|
221
167
|
// Test connection
|
|
222
168
|
console.log()
|
|
223
169
|
log('Testing connection...', 'yellow')
|
|
224
170
|
|
|
225
|
-
const result = await testConnection(apiKey, projectSlug,
|
|
171
|
+
const result = await testConnection(apiKey, projectSlug, apiUrl)
|
|
226
172
|
|
|
227
173
|
if (!result.success) {
|
|
228
174
|
log(`Connection failed: ${result.error}`, 'red')
|
|
229
|
-
log('Please check your API key and
|
|
175
|
+
log('Please check your API key, project slug, and API URL.', 'dim')
|
|
230
176
|
process.exit(1)
|
|
231
177
|
}
|
|
232
178
|
|
|
233
|
-
log(`Connected to: ${result.project.name}`, 'green')
|
|
179
|
+
log(`Connected to project: ${result.project.name}`, 'green')
|
|
234
180
|
|
|
235
181
|
// Save config
|
|
236
182
|
const config = {
|
|
237
183
|
api_key: apiKey,
|
|
238
184
|
project_slug: projectSlug,
|
|
239
|
-
api_url:
|
|
185
|
+
api_url: apiUrl,
|
|
240
186
|
created_at: new Date().toISOString()
|
|
241
187
|
}
|
|
242
188
|
|
|
243
189
|
saveConfig(config)
|
|
244
190
|
log('Configuration saved!', 'green')
|
|
245
191
|
|
|
246
|
-
// Step 3:
|
|
192
|
+
// Step 3: Optional credentials
|
|
247
193
|
console.log()
|
|
248
|
-
|
|
194
|
+
log('Step 3: Local Credentials (optional)', 'bright')
|
|
195
|
+
log('For image generation and CMS publishing, create:', 'dim')
|
|
196
|
+
log(` ${CREDENTIALS_FILE}`, 'cyan')
|
|
249
197
|
console.log()
|
|
250
|
-
log('Set up local integrations for:', 'dim')
|
|
251
|
-
log(' - Image generation (fal.ai, Gemini, wiro.ai)', 'dim')
|
|
252
|
-
log(' - WordPress publishing', 'dim')
|
|
253
|
-
log(' - Ghost CMS publishing', 'dim')
|
|
254
|
-
log(' - Webhooks (Make, n8n, Zapier, Slack)', 'dim')
|
|
255
|
-
console.log()
|
|
256
|
-
|
|
257
|
-
const setupCreds = await prompt('Configure integrations now? (y/N): ')
|
|
258
198
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
199
|
+
log('Example credentials.json:', 'dim')
|
|
200
|
+
console.log(`{
|
|
201
|
+
"image_provider": "fal",
|
|
202
|
+
"fal": { "api_key": "YOUR_FAL_KEY" },
|
|
203
|
+
"wordpress": {
|
|
204
|
+
"site_url": "https://your-site.com",
|
|
205
|
+
"secret_key": "FROM_PLUGIN_SETTINGS"
|
|
206
|
+
},
|
|
207
|
+
"ghost": {
|
|
208
|
+
"api_url": "https://your-ghost.com",
|
|
209
|
+
"admin_api_key": "YOUR_GHOST_ADMIN_KEY"
|
|
263
210
|
}
|
|
211
|
+
}`)
|
|
264
212
|
|
|
265
|
-
|
|
213
|
+
console.log()
|
|
266
214
|
logHeader('Setup Complete!')
|
|
267
215
|
|
|
268
216
|
log('Add Suparank to your AI client:', 'bright')
|
|
269
217
|
console.log()
|
|
270
|
-
|
|
271
|
-
log('For Claude Desktop:', 'cyan')
|
|
272
|
-
log('Edit ~/.config/claude/claude_desktop_config.json:', 'dim')
|
|
218
|
+
log('For Claude Desktop (claude_desktop_config.json):', 'dim')
|
|
273
219
|
console.log(`{
|
|
274
220
|
"mcpServers": {
|
|
275
221
|
"suparank": {
|
|
@@ -280,8 +226,7 @@ async function runSetup() {
|
|
|
280
226
|
}`)
|
|
281
227
|
|
|
282
228
|
console.log()
|
|
283
|
-
log('For Cursor:', '
|
|
284
|
-
log('Add to your MCP settings:', 'dim')
|
|
229
|
+
log('For Cursor (settings.json):', 'dim')
|
|
285
230
|
console.log(`{
|
|
286
231
|
"mcpServers": {
|
|
287
232
|
"suparank": {
|
|
@@ -293,133 +238,11 @@ async function runSetup() {
|
|
|
293
238
|
|
|
294
239
|
console.log()
|
|
295
240
|
log('Commands:', 'bright')
|
|
296
|
-
log(' npx suparank
|
|
297
|
-
log(' npx suparank setup
|
|
298
|
-
log(' npx suparank
|
|
299
|
-
log(' npx suparank
|
|
300
|
-
log(' npx suparank
|
|
301
|
-
log(' npx suparank clear Clear session', 'dim')
|
|
302
|
-
console.log()
|
|
303
|
-
log('Documentation: https://suparank.io/docs', 'cyan')
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
async function runCredentialsSetup() {
|
|
307
|
-
logHeader('Configure Integrations')
|
|
308
|
-
|
|
309
|
-
const credentials = loadCredentials()
|
|
310
|
-
|
|
311
|
-
// Image Generation
|
|
312
|
-
log('Image Generation', 'bright')
|
|
313
|
-
log('Generate AI images for your blog posts', 'dim')
|
|
314
|
-
console.log()
|
|
315
|
-
log('Providers:', 'cyan')
|
|
316
|
-
log(' 1. fal.ai (recommended) - Fast, high quality', 'dim')
|
|
317
|
-
log(' 2. wiro.ai - Google Imagen via API', 'dim')
|
|
318
|
-
log(' 3. Gemini - Google AI directly', 'dim')
|
|
319
|
-
log(' 4. Skip', 'dim')
|
|
320
|
-
console.log()
|
|
321
|
-
|
|
322
|
-
const imageChoice = await prompt('Choose provider (1-4): ')
|
|
323
|
-
|
|
324
|
-
if (imageChoice === '1') {
|
|
325
|
-
const apiKey = await prompt('fal.ai API key: ')
|
|
326
|
-
if (apiKey) {
|
|
327
|
-
credentials.image_provider = 'fal'
|
|
328
|
-
credentials.fal = { api_key: apiKey }
|
|
329
|
-
log('fal.ai configured!', 'green')
|
|
330
|
-
}
|
|
331
|
-
} else if (imageChoice === '2') {
|
|
332
|
-
const apiKey = await prompt('wiro.ai API key: ')
|
|
333
|
-
const apiSecret = await prompt('wiro.ai API secret: ')
|
|
334
|
-
if (apiKey && apiSecret) {
|
|
335
|
-
credentials.image_provider = 'wiro'
|
|
336
|
-
credentials.wiro = {
|
|
337
|
-
api_key: apiKey,
|
|
338
|
-
api_secret: apiSecret,
|
|
339
|
-
model: 'google/nano-banana-pro'
|
|
340
|
-
}
|
|
341
|
-
log('wiro.ai configured!', 'green')
|
|
342
|
-
}
|
|
343
|
-
} else if (imageChoice === '3') {
|
|
344
|
-
const apiKey = await prompt('Google AI API key: ')
|
|
345
|
-
if (apiKey) {
|
|
346
|
-
credentials.image_provider = 'gemini'
|
|
347
|
-
credentials.gemini = { api_key: apiKey }
|
|
348
|
-
log('Gemini configured!', 'green')
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// WordPress
|
|
353
|
-
console.log()
|
|
354
|
-
log('WordPress Publishing', 'bright')
|
|
355
|
-
log('Publish directly to your WordPress site', 'dim')
|
|
356
|
-
console.log()
|
|
357
|
-
|
|
358
|
-
const setupWP = await prompt('Configure WordPress? (y/N): ')
|
|
359
|
-
if (setupWP.toLowerCase() === 'y') {
|
|
360
|
-
log('Install the Suparank Connector plugin from:', 'dim')
|
|
361
|
-
log(' https://suparank.io/wordpress-plugin', 'cyan')
|
|
362
|
-
console.log()
|
|
363
|
-
|
|
364
|
-
const siteUrl = await prompt('WordPress site URL (https://your-site.com): ')
|
|
365
|
-
const secretKey = await prompt('Plugin secret key (from plugin settings): ')
|
|
366
|
-
|
|
367
|
-
if (siteUrl && secretKey) {
|
|
368
|
-
credentials.wordpress = {
|
|
369
|
-
site_url: siteUrl.replace(/\/$/, ''),
|
|
370
|
-
secret_key: secretKey
|
|
371
|
-
}
|
|
372
|
-
log('WordPress configured!', 'green')
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Ghost
|
|
377
|
-
console.log()
|
|
378
|
-
log('Ghost CMS Publishing', 'bright')
|
|
379
|
-
log('Publish directly to your Ghost blog', 'dim')
|
|
380
|
-
console.log()
|
|
381
|
-
|
|
382
|
-
const setupGhost = await prompt('Configure Ghost? (y/N): ')
|
|
383
|
-
if (setupGhost.toLowerCase() === 'y') {
|
|
384
|
-
const apiUrl = await prompt('Ghost site URL (https://your-ghost.com): ')
|
|
385
|
-
const adminKey = await prompt('Admin API key (from Ghost settings): ')
|
|
386
|
-
|
|
387
|
-
if (apiUrl && adminKey) {
|
|
388
|
-
credentials.ghost = {
|
|
389
|
-
api_url: apiUrl.replace(/\/$/, ''),
|
|
390
|
-
admin_api_key: adminKey
|
|
391
|
-
}
|
|
392
|
-
log('Ghost configured!', 'green')
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Webhooks
|
|
397
|
-
console.log()
|
|
398
|
-
log('Webhooks (Optional)', 'bright')
|
|
399
|
-
log('Send notifications to Make, n8n, Zapier, or Slack', 'dim')
|
|
400
|
-
console.log()
|
|
401
|
-
|
|
402
|
-
const setupWebhooks = await prompt('Configure webhooks? (y/N): ')
|
|
403
|
-
if (setupWebhooks.toLowerCase() === 'y') {
|
|
404
|
-
credentials.webhooks = credentials.webhooks || {}
|
|
405
|
-
|
|
406
|
-
const slackUrl = await prompt('Slack webhook URL (or Enter to skip): ')
|
|
407
|
-
if (slackUrl) credentials.webhooks.slack_url = slackUrl
|
|
408
|
-
|
|
409
|
-
const makeUrl = await prompt('Make.com webhook URL (or Enter to skip): ')
|
|
410
|
-
if (makeUrl) credentials.webhooks.make_url = makeUrl
|
|
411
|
-
|
|
412
|
-
const zapierUrl = await prompt('Zapier webhook URL (or Enter to skip): ')
|
|
413
|
-
if (zapierUrl) credentials.webhooks.zapier_url = zapierUrl
|
|
414
|
-
|
|
415
|
-
log('Webhooks configured!', 'green')
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Save credentials
|
|
419
|
-
saveCredentials(credentials)
|
|
420
|
-
console.log()
|
|
421
|
-
log('Credentials saved to ~/.suparank/credentials.json', 'green')
|
|
422
|
-
log('File permissions set to owner-only (600)', 'dim')
|
|
241
|
+
log(' npx suparank - Run MCP server', 'dim')
|
|
242
|
+
log(' npx suparank setup - Run setup again', 'dim')
|
|
243
|
+
log(' npx suparank test - Test API connection', 'dim')
|
|
244
|
+
log(' npx suparank session - View session state', 'dim')
|
|
245
|
+
log(' npx suparank clear - Clear session', 'dim')
|
|
423
246
|
}
|
|
424
247
|
|
|
425
248
|
async function runTest() {
|
|
@@ -440,22 +263,20 @@ async function runTest() {
|
|
|
440
263
|
|
|
441
264
|
if (result.success) {
|
|
442
265
|
log(`Success! Connected to: ${result.project.name}`, 'green')
|
|
443
|
-
console.log()
|
|
444
266
|
|
|
445
267
|
// Check credentials
|
|
446
268
|
const creds = loadCredentials()
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
269
|
+
if (creds) {
|
|
270
|
+
const configured = []
|
|
271
|
+
if (creds.wordpress?.secret_key) configured.push('WordPress')
|
|
272
|
+
if (creds.ghost?.admin_api_key) configured.push('Ghost')
|
|
273
|
+
if (creds[creds.image_provider]?.api_key) configured.push(`Images (${creds.image_provider})`)
|
|
274
|
+
|
|
275
|
+
if (configured.length > 0) {
|
|
276
|
+
log(`Local integrations: ${configured.join(', ')}`, 'green')
|
|
277
|
+
}
|
|
456
278
|
} else {
|
|
457
|
-
log('No local
|
|
458
|
-
log('Run: npx suparank credentials', 'dim')
|
|
279
|
+
log('No local credentials configured (optional)', 'dim')
|
|
459
280
|
}
|
|
460
281
|
} else {
|
|
461
282
|
log(`Connection failed: ${result.error}`, 'red')
|
|
@@ -510,273 +331,21 @@ function clearSession() {
|
|
|
510
331
|
}
|
|
511
332
|
}
|
|
512
333
|
|
|
513
|
-
function
|
|
514
|
-
const packageJson = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'))
|
|
515
|
-
log(`Suparank MCP v${packageJson.version}`, 'cyan')
|
|
516
|
-
log('https://suparank.io', 'dim')
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function loadStats() {
|
|
520
|
-
try {
|
|
521
|
-
if (fs.existsSync(STATS_FILE)) {
|
|
522
|
-
return JSON.parse(fs.readFileSync(STATS_FILE, 'utf-8'))
|
|
523
|
-
}
|
|
524
|
-
} catch (e) {}
|
|
525
|
-
return { tool_calls: 0, images_generated: 0, articles_created: 0, words_written: 0 }
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function loadSession() {
|
|
529
|
-
try {
|
|
530
|
-
if (fs.existsSync(SESSION_FILE)) {
|
|
531
|
-
return JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'))
|
|
532
|
-
}
|
|
533
|
-
} catch (e) {}
|
|
534
|
-
return null
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
function countSavedContent() {
|
|
538
|
-
try {
|
|
539
|
-
if (fs.existsSync(CONTENT_DIR)) {
|
|
540
|
-
const folders = fs.readdirSync(CONTENT_DIR).filter(f => {
|
|
541
|
-
const stat = fs.statSync(path.join(CONTENT_DIR, f))
|
|
542
|
-
return stat.isDirectory()
|
|
543
|
-
})
|
|
544
|
-
return folders.length
|
|
545
|
-
}
|
|
546
|
-
} catch (e) {}
|
|
547
|
-
return 0
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function getRecentContent(limit = 3) {
|
|
551
|
-
try {
|
|
552
|
-
if (fs.existsSync(CONTENT_DIR)) {
|
|
553
|
-
const folders = fs.readdirSync(CONTENT_DIR)
|
|
554
|
-
.filter(f => fs.statSync(path.join(CONTENT_DIR, f)).isDirectory())
|
|
555
|
-
.map(f => {
|
|
556
|
-
const metaPath = path.join(CONTENT_DIR, f, 'metadata.json')
|
|
557
|
-
let meta = { title: f }
|
|
558
|
-
try {
|
|
559
|
-
if (fs.existsSync(metaPath)) {
|
|
560
|
-
meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'))
|
|
561
|
-
}
|
|
562
|
-
} catch (e) {}
|
|
563
|
-
return { folder: f, ...meta }
|
|
564
|
-
})
|
|
565
|
-
.sort((a, b) => (b.savedAt || '').localeCompare(a.savedAt || ''))
|
|
566
|
-
.slice(0, limit)
|
|
567
|
-
return folders
|
|
568
|
-
}
|
|
569
|
-
} catch (e) {}
|
|
570
|
-
return []
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
async function displayDashboard(config, project) {
|
|
574
|
-
const packageJson = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'))
|
|
575
|
-
const credentials = loadCredentials()
|
|
576
|
-
const session = loadSession()
|
|
577
|
-
const stats = loadStats()
|
|
578
|
-
const savedCount = countSavedContent()
|
|
579
|
-
const recentContent = getRecentContent(3)
|
|
580
|
-
const projectConfig = project?.config || {}
|
|
581
|
-
|
|
582
|
-
// Header
|
|
583
|
-
console.log()
|
|
584
|
-
log('╔══════════════════════════════════════════════════════════════════════════════╗', 'cyan')
|
|
585
|
-
log('║ 🚀 SUPARANK MCP SERVER ║', 'cyan')
|
|
586
|
-
log('╚══════════════════════════════════════════════════════════════════════════════╝', 'cyan')
|
|
587
|
-
console.log()
|
|
588
|
-
|
|
589
|
-
// Version and project info
|
|
590
|
-
log(` Version: ${packageJson.version}`, 'dim')
|
|
591
|
-
log(` Project: ${colors.bright}${project?.name || config.project_slug}${colors.reset}`, 'reset')
|
|
592
|
-
log(` URL: ${projectConfig.site?.url || 'Not set'}`, 'dim')
|
|
593
|
-
console.log()
|
|
594
|
-
|
|
595
|
-
// Project Settings Box
|
|
596
|
-
log('┌─────────────────────────────────────────────────────────────────────────────┐', 'yellow')
|
|
597
|
-
log('│ 📋 PROJECT SETTINGS (from Supabase) │', 'yellow')
|
|
598
|
-
log('├─────────────────────────────────────────────────────────────────────────────┤', 'yellow')
|
|
599
|
-
log(`│ Word Count: ${String(projectConfig.content?.default_word_count || 'Not set').padEnd(15)} │ Brand Voice: ${String(projectConfig.brand?.voice || 'Not set').substring(0, 25).padEnd(25)}│`, 'reset')
|
|
600
|
-
log(`│ Reading Level: ${String(projectConfig.content?.reading_level ? `Grade ${projectConfig.content.reading_level}` : 'Not set').padEnd(15)} │ Target: ${String(projectConfig.brand?.target_audience || 'Not set').substring(0, 25).padEnd(25)}│`, 'reset')
|
|
601
|
-
log(`│ Include Images: ${String(projectConfig.content?.include_images ? 'Yes' : 'No').padEnd(15)} │ Niche: ${String(projectConfig.site?.niche || 'Not set').substring(0, 25).padEnd(25)}│`, 'reset')
|
|
602
|
-
log(`│ Keywords: ${String((projectConfig.seo?.primary_keywords || []).slice(0, 3).join(', ') || 'Not set').substring(0, 56).padEnd(56)}│`, 'reset')
|
|
603
|
-
log('└─────────────────────────────────────────────────────────────────────────────┘', 'yellow')
|
|
604
|
-
console.log()
|
|
605
|
-
|
|
606
|
-
// Integrations Status
|
|
607
|
-
log('┌─────────────────────────────────────────────────────────────────────────────┐', 'green')
|
|
608
|
-
log('│ 🔌 INTEGRATIONS │', 'green')
|
|
609
|
-
log('├─────────────────────────────────────────────────────────────────────────────┤', 'green')
|
|
610
|
-
|
|
611
|
-
const wpStatus = credentials.wordpress?.secret_key || credentials.wordpress?.app_password ? '✅ Enabled' : '❌ Not configured'
|
|
612
|
-
const ghostStatus = credentials.ghost?.admin_api_key ? '✅ Enabled' : '❌ Not configured'
|
|
613
|
-
const imageStatus = credentials[credentials.image_provider]?.api_key ? `✅ ${credentials.image_provider}` : '❌ Not configured'
|
|
614
|
-
const webhookStatus = credentials.webhooks && Object.values(credentials.webhooks).some(Boolean) ? '✅ Enabled' : '❌ Not configured'
|
|
615
|
-
const externalMcps = credentials.external_mcps?.length || 0
|
|
616
|
-
|
|
617
|
-
log(`│ WordPress: ${wpStatus.padEnd(20)} │ Ghost CMS: ${ghostStatus.padEnd(20)}│`, 'reset')
|
|
618
|
-
log(`│ Image Gen: ${imageStatus.padEnd(20)} │ Webhooks: ${webhookStatus.padEnd(20)}│`, 'reset')
|
|
619
|
-
log(`│ External MCPs: ${String(externalMcps > 0 ? `✅ ${externalMcps} configured` : '❌ None').padEnd(58)}│`, 'reset')
|
|
620
|
-
log('└─────────────────────────────────────────────────────────────────────────────┘', 'green')
|
|
621
|
-
console.log()
|
|
622
|
-
|
|
623
|
-
// Session Status
|
|
624
|
-
log('┌─────────────────────────────────────────────────────────────────────────────┐', 'magenta')
|
|
625
|
-
log('│ 📝 CURRENT SESSION │', 'magenta')
|
|
626
|
-
log('├─────────────────────────────────────────────────────────────────────────────┤', 'magenta')
|
|
627
|
-
|
|
628
|
-
if (session && session.articles?.length > 0) {
|
|
629
|
-
const totalWords = session.articles.reduce((sum, a) => sum + (a.wordCount || 0), 0)
|
|
630
|
-
const unpublished = session.articles.filter(a => !a.published).length
|
|
631
|
-
log(`│ Articles: ${String(session.articles.length).padEnd(5)} │ Words: ${String(totalWords).padEnd(8)} │ Unpublished: ${String(unpublished).padEnd(14)}│`, 'reset')
|
|
632
|
-
|
|
633
|
-
if (session.articles.length > 0) {
|
|
634
|
-
const latest = session.articles[session.articles.length - 1]
|
|
635
|
-
log(`│ Latest: ${String(`"${latest.title?.substring(0, 45) || 'Untitled'}..."`).padEnd(63)}│`, 'reset')
|
|
636
|
-
}
|
|
637
|
-
} else {
|
|
638
|
-
log(`│ No active session - Start with: "Create a blog post about [topic]" │`, 'dim')
|
|
639
|
-
}
|
|
640
|
-
log('└─────────────────────────────────────────────────────────────────────────────┘', 'magenta')
|
|
641
|
-
console.log()
|
|
642
|
-
|
|
643
|
-
// Recent Content
|
|
644
|
-
if (recentContent.length > 0) {
|
|
645
|
-
log('┌─────────────────────────────────────────────────────────────────────────────┐', 'blue')
|
|
646
|
-
log('│ 📚 RECENT CONTENT │', 'blue')
|
|
647
|
-
log('├─────────────────────────────────────────────────────────────────────────────┤', 'blue')
|
|
648
|
-
recentContent.forEach((content, i) => {
|
|
649
|
-
const title = (content.title || content.folder).substring(0, 50)
|
|
650
|
-
const words = content.wordCount || '?'
|
|
651
|
-
const date = content.savedAt ? new Date(content.savedAt).toLocaleDateString() : '?'
|
|
652
|
-
log(`│ ${i + 1}. ${title.padEnd(50)} ${String(words + ' words').padEnd(12)} ${date.padEnd(10)}│`, 'reset')
|
|
653
|
-
})
|
|
654
|
-
log(`│ Total saved: ${String(savedCount + ' articles').padEnd(60)}│`, 'dim')
|
|
655
|
-
log('└─────────────────────────────────────────────────────────────────────────────┘', 'blue')
|
|
656
|
-
console.log()
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// Stats (if available)
|
|
660
|
-
if (stats.tool_calls > 0 || stats.articles_created > 0) {
|
|
661
|
-
log('┌─────────────────────────────────────────────────────────────────────────────┐', 'cyan')
|
|
662
|
-
log('│ 📊 USAGE STATS │', 'cyan')
|
|
663
|
-
log('├─────────────────────────────────────────────────────────────────────────────┤', 'cyan')
|
|
664
|
-
log(`│ Tool Calls: ${String(stats.tool_calls).padEnd(10)} │ Articles: ${String(stats.articles_created).padEnd(10)} │ Images: ${String(stats.images_generated).padEnd(10)}│`, 'reset')
|
|
665
|
-
log(`│ Words Written: ${String(stats.words_written?.toLocaleString() || 0).padEnd(58)}│`, 'reset')
|
|
666
|
-
log('└─────────────────────────────────────────────────────────────────────────────┘', 'cyan')
|
|
667
|
-
console.log()
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// Ready message
|
|
671
|
-
log('─────────────────────────────────────────────────────────────────────────────', 'dim')
|
|
672
|
-
log(' MCP Server ready. Waiting for AI client connection...', 'green')
|
|
673
|
-
log(' Tip: Say "Create a blog post about [topic]" to start', 'dim')
|
|
674
|
-
log('─────────────────────────────────────────────────────────────────────────────', 'dim')
|
|
675
|
-
console.log()
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
async function checkForUpdates(showCurrent = false) {
|
|
679
|
-
const packageJson = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'))
|
|
680
|
-
const currentVersion = packageJson.version
|
|
681
|
-
|
|
682
|
-
if (showCurrent) {
|
|
683
|
-
log(`Current version: ${currentVersion}`, 'dim')
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
try {
|
|
687
|
-
const response = await fetch('https://registry.npmjs.org/suparank/latest')
|
|
688
|
-
if (response.ok) {
|
|
689
|
-
const data = await response.json()
|
|
690
|
-
return { current: currentVersion, latest: data.version }
|
|
691
|
-
}
|
|
692
|
-
} catch (e) {
|
|
693
|
-
// Ignore fetch errors
|
|
694
|
-
}
|
|
695
|
-
return { current: currentVersion, latest: null }
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
async function runUpdate() {
|
|
699
|
-
logHeader('Suparank Update')
|
|
700
|
-
|
|
701
|
-
log('Checking for updates...', 'yellow')
|
|
702
|
-
const { current, latest } = await checkForUpdates(true)
|
|
703
|
-
|
|
704
|
-
if (!latest) {
|
|
705
|
-
log('Could not check for updates. Please try again later.', 'red')
|
|
706
|
-
return
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
log(`Latest version: ${latest}`, 'dim')
|
|
710
|
-
console.log()
|
|
711
|
-
|
|
712
|
-
if (current === latest) {
|
|
713
|
-
log('You are already on the latest version!', 'green')
|
|
714
|
-
return
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// Show what will be preserved
|
|
718
|
-
log('Your data is safe:', 'cyan')
|
|
719
|
-
log(' ~/.suparank/config.json (API key, project)', 'dim')
|
|
720
|
-
log(' ~/.suparank/credentials.json (WordPress, Ghost, etc.)', 'dim')
|
|
721
|
-
log(' ~/.suparank/session.json (Current session)', 'dim')
|
|
722
|
-
log(' ~/.suparank/content/ (Saved articles)', 'dim')
|
|
723
|
-
console.log()
|
|
724
|
-
|
|
725
|
-
log(`Updating from v${current} to v${latest}...`, 'yellow')
|
|
726
|
-
console.log()
|
|
727
|
-
|
|
728
|
-
// Clear npx cache and reinstall
|
|
729
|
-
const { execSync } = await import('child_process')
|
|
730
|
-
|
|
731
|
-
try {
|
|
732
|
-
// Clear the npx cache for suparank
|
|
733
|
-
log('Clearing cache...', 'dim')
|
|
734
|
-
execSync('npx clear-npx-cache 2>/dev/null || true', { stdio: 'pipe' })
|
|
735
|
-
|
|
736
|
-
// Force npx to fetch the latest version
|
|
737
|
-
log('Downloading latest version...', 'dim')
|
|
738
|
-
execSync('npm cache clean --force 2>/dev/null || true', { stdio: 'pipe' })
|
|
739
|
-
|
|
740
|
-
console.log()
|
|
741
|
-
log(`Updated to v${latest}!`, 'green')
|
|
742
|
-
console.log()
|
|
743
|
-
log('Run "npx suparank@latest" to use the new version.', 'cyan')
|
|
744
|
-
log('Or restart your AI client to pick up changes.', 'dim')
|
|
745
|
-
} catch (e) {
|
|
746
|
-
log(`Update failed: ${e.message}`, 'red')
|
|
747
|
-
console.log()
|
|
748
|
-
log('Try manually:', 'dim')
|
|
749
|
-
log(' npm cache clean --force', 'cyan')
|
|
750
|
-
log(' npx suparank@latest', 'cyan')
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
async function runMCP() {
|
|
334
|
+
function runMCP() {
|
|
755
335
|
const config = loadConfig()
|
|
756
336
|
|
|
757
337
|
if (!config) {
|
|
758
338
|
log('No configuration found. Running setup...', 'yellow')
|
|
759
339
|
console.log()
|
|
760
|
-
|
|
340
|
+
runSetup()
|
|
761
341
|
return
|
|
762
342
|
}
|
|
763
343
|
|
|
764
|
-
//
|
|
765
|
-
let project = null
|
|
766
|
-
try {
|
|
767
|
-
const result = await testConnection(config.api_key, config.project_slug, config.api_url)
|
|
768
|
-
if (result.success) {
|
|
769
|
-
project = result.project
|
|
770
|
-
}
|
|
771
|
-
} catch (e) {
|
|
772
|
-
// Continue without project data
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// Display dashboard
|
|
776
|
-
await displayDashboard(config, project)
|
|
777
|
-
|
|
778
|
-
// Find the MCP client script
|
|
344
|
+
// Find the MCP client script (modular version)
|
|
779
345
|
const mcpClientPaths = [
|
|
346
|
+
path.join(import.meta.dirname, '..', 'mcp-client', 'index.js'),
|
|
347
|
+
path.join(process.cwd(), 'mcp-client', 'index.js'),
|
|
348
|
+
// Legacy fallback
|
|
780
349
|
path.join(import.meta.dirname, '..', 'mcp-client.js'),
|
|
781
350
|
path.join(process.cwd(), 'mcp-client.js')
|
|
782
351
|
]
|
|
@@ -790,7 +359,7 @@ async function runMCP() {
|
|
|
790
359
|
}
|
|
791
360
|
|
|
792
361
|
if (!mcpClientPath) {
|
|
793
|
-
log('Error: mcp-client
|
|
362
|
+
log('Error: mcp-client not found', 'red')
|
|
794
363
|
process.exit(1)
|
|
795
364
|
}
|
|
796
365
|
|
|
@@ -820,10 +389,6 @@ switch (command) {
|
|
|
820
389
|
case 'setup':
|
|
821
390
|
runSetup()
|
|
822
391
|
break
|
|
823
|
-
case 'credentials':
|
|
824
|
-
case 'creds':
|
|
825
|
-
runCredentialsSetup()
|
|
826
|
-
break
|
|
827
392
|
case 'test':
|
|
828
393
|
runTest()
|
|
829
394
|
break
|
|
@@ -833,35 +398,19 @@ switch (command) {
|
|
|
833
398
|
case 'clear':
|
|
834
399
|
clearSession()
|
|
835
400
|
break
|
|
836
|
-
case 'update':
|
|
837
|
-
case 'upgrade':
|
|
838
|
-
runUpdate()
|
|
839
|
-
break
|
|
840
|
-
case 'version':
|
|
841
|
-
case '-v':
|
|
842
|
-
case '--version':
|
|
843
|
-
showVersion()
|
|
844
|
-
break
|
|
845
401
|
case 'help':
|
|
846
402
|
case '--help':
|
|
847
403
|
case '-h':
|
|
848
404
|
logHeader('Suparank CLI')
|
|
849
|
-
log('
|
|
850
|
-
console.log()
|
|
851
|
-
log('Usage: npx suparank [command]', 'cyan')
|
|
405
|
+
log('Usage: npx suparank [command]', 'dim')
|
|
852
406
|
console.log()
|
|
853
407
|
log('Commands:', 'bright')
|
|
854
|
-
log(' (none)
|
|
855
|
-
log(' setup
|
|
856
|
-
log('
|
|
857
|
-
log('
|
|
858
|
-
log('
|
|
859
|
-
log('
|
|
860
|
-
log(' update Check for updates and install latest', 'dim')
|
|
861
|
-
log(' version Show version', 'dim')
|
|
862
|
-
log(' help Show this help message', 'dim')
|
|
863
|
-
console.log()
|
|
864
|
-
log('Documentation: https://suparank.io/docs', 'cyan')
|
|
408
|
+
log(' (none) Run MCP server (default)', 'dim')
|
|
409
|
+
log(' setup Run setup wizard', 'dim')
|
|
410
|
+
log(' test Test API connection', 'dim')
|
|
411
|
+
log(' session View current session state', 'dim')
|
|
412
|
+
log(' clear Clear session state', 'dim')
|
|
413
|
+
log(' help Show this help message', 'dim')
|
|
865
414
|
break
|
|
866
415
|
default:
|
|
867
416
|
runMCP()
|