suparank 1.2.3 → 1.2.4
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 +177 -2
- package/mcp-client.js +57 -0
- package/package.json +1 -1
package/bin/suparank.js
CHANGED
|
@@ -23,6 +23,8 @@ const SUPARANK_DIR = path.join(os.homedir(), '.suparank')
|
|
|
23
23
|
const CONFIG_FILE = path.join(SUPARANK_DIR, 'config.json')
|
|
24
24
|
const CREDENTIALS_FILE = path.join(SUPARANK_DIR, 'credentials.json')
|
|
25
25
|
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')
|
|
26
28
|
|
|
27
29
|
// Production API URL
|
|
28
30
|
const DEFAULT_API_URL = 'https://api.suparank.io'
|
|
@@ -514,6 +516,165 @@ function showVersion() {
|
|
|
514
516
|
log('https://suparank.io', 'dim')
|
|
515
517
|
}
|
|
516
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
|
+
|
|
517
678
|
async function checkForUpdates(showCurrent = false) {
|
|
518
679
|
const packageJson = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'))
|
|
519
680
|
const currentVersion = packageJson.version
|
|
@@ -590,16 +751,30 @@ async function runUpdate() {
|
|
|
590
751
|
}
|
|
591
752
|
}
|
|
592
753
|
|
|
593
|
-
function runMCP() {
|
|
754
|
+
async function runMCP() {
|
|
594
755
|
const config = loadConfig()
|
|
595
756
|
|
|
596
757
|
if (!config) {
|
|
597
758
|
log('No configuration found. Running setup...', 'yellow')
|
|
598
759
|
console.log()
|
|
599
|
-
runSetup()
|
|
760
|
+
await runSetup()
|
|
600
761
|
return
|
|
601
762
|
}
|
|
602
763
|
|
|
764
|
+
// Fetch project data for dashboard
|
|
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
|
+
|
|
603
778
|
// Find the MCP client script
|
|
604
779
|
const mcpClientPaths = [
|
|
605
780
|
path.join(import.meta.dirname, '..', 'mcp-client.js'),
|
package/mcp-client.js
CHANGED
|
@@ -144,6 +144,47 @@ function ensureContentDir() {
|
|
|
144
144
|
return dir
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Get the path to the stats file (~/.suparank/stats.json)
|
|
149
|
+
*/
|
|
150
|
+
function getStatsFile() {
|
|
151
|
+
return path.join(getSuparankDir(), 'stats.json')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Load usage stats
|
|
156
|
+
*/
|
|
157
|
+
function loadStats() {
|
|
158
|
+
try {
|
|
159
|
+
const file = getStatsFile()
|
|
160
|
+
if (fs.existsSync(file)) {
|
|
161
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8'))
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {}
|
|
164
|
+
return { tool_calls: 0, images_generated: 0, articles_created: 0, words_written: 0 }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Save usage stats
|
|
169
|
+
*/
|
|
170
|
+
function saveStats(stats) {
|
|
171
|
+
try {
|
|
172
|
+
ensureSuparankDir()
|
|
173
|
+
fs.writeFileSync(getStatsFile(), JSON.stringify(stats, null, 2))
|
|
174
|
+
} catch (e) {
|
|
175
|
+
log(`Error saving stats: ${e.message}`)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Increment a stat counter
|
|
181
|
+
*/
|
|
182
|
+
function incrementStat(key, amount = 1) {
|
|
183
|
+
const stats = loadStats()
|
|
184
|
+
stats[key] = (stats[key] || 0) + amount
|
|
185
|
+
saveStats(stats)
|
|
186
|
+
}
|
|
187
|
+
|
|
147
188
|
/**
|
|
148
189
|
* Generate a slug from title for folder naming
|
|
149
190
|
*/
|
|
@@ -1922,6 +1963,10 @@ ${plan.steps[0].instruction}
|
|
|
1922
1963
|
// Add to articles array (not overwriting previous articles!)
|
|
1923
1964
|
sessionState.articles.push(newArticle)
|
|
1924
1965
|
|
|
1966
|
+
// Track stats
|
|
1967
|
+
incrementStat('articles_created')
|
|
1968
|
+
incrementStat('words_written', wordCount)
|
|
1969
|
+
|
|
1925
1970
|
// Also keep in current working fields for backwards compatibility
|
|
1926
1971
|
sessionState.title = title
|
|
1927
1972
|
sessionState.article = content
|
|
@@ -2694,6 +2739,9 @@ async function executeImageGeneration(args) {
|
|
|
2694
2739
|
// Persist session to file
|
|
2695
2740
|
saveSession()
|
|
2696
2741
|
|
|
2742
|
+
// Track stats
|
|
2743
|
+
incrementStat('images_generated')
|
|
2744
|
+
|
|
2697
2745
|
const imageNumber = 1 + sessionState.inlineImages.length
|
|
2698
2746
|
const totalImages = sessionState.currentWorkflow?.settings?.total_images || 1
|
|
2699
2747
|
const imageType = imageNumber === 1 ? 'Cover Image' : `Inline Image ${imageNumber - 1}`
|
|
@@ -2757,6 +2805,9 @@ ${imageNumber < totalImages ? `\n**Next:** Generate ${totalImages - imageNumber}
|
|
|
2757
2805
|
// Return base64 data URI
|
|
2758
2806
|
const dataUri = `data:${mimeType};base64,${imageData}`
|
|
2759
2807
|
|
|
2808
|
+
// Track stats
|
|
2809
|
+
incrementStat('images_generated')
|
|
2810
|
+
|
|
2760
2811
|
return {
|
|
2761
2812
|
content: [{
|
|
2762
2813
|
type: 'text',
|
|
@@ -2871,6 +2922,9 @@ ${imageNumber < totalImages ? `\n**Next:** Generate ${totalImages - imageNumber}
|
|
|
2871
2922
|
// Persist session to file
|
|
2872
2923
|
saveSession()
|
|
2873
2924
|
|
|
2925
|
+
// Track stats
|
|
2926
|
+
incrementStat('images_generated')
|
|
2927
|
+
|
|
2874
2928
|
const imageNumber = 1 + sessionState.inlineImages.length
|
|
2875
2929
|
const totalImages = sessionState.currentWorkflow?.settings?.total_images || 1
|
|
2876
2930
|
const imageType = imageNumber === 1 ? 'Cover Image' : `Inline Image ${imageNumber - 1}`
|
|
@@ -3430,6 +3484,9 @@ async function main() {
|
|
|
3430
3484
|
progress('Tool', `Executing ${name}`)
|
|
3431
3485
|
log(`Executing tool: ${name}`)
|
|
3432
3486
|
|
|
3487
|
+
// Track tool call stats
|
|
3488
|
+
incrementStat('tool_calls')
|
|
3489
|
+
|
|
3433
3490
|
// Check if this is an orchestrator tool
|
|
3434
3491
|
const orchestratorTool = ORCHESTRATOR_TOOLS.find(t => t.name === name)
|
|
3435
3492
|
|
package/package.json
CHANGED